pax_global_header00006660000000000000000000000064147577076340014536gustar00rootroot0000000000000052 comment=a08e0f6686d467bb8b9e4715b1f1835f12984fb0 gifsicle-1.96/000077500000000000000000000000001475770763400132625ustar00rootroot00000000000000gifsicle-1.96/.appveyor.yml000066400000000000000000000004211475770763400157250ustar00rootroot00000000000000os: Visual Studio 2015 build_script: - CALL "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\vcvars32.bat" - cd "%APPVEYOR_BUILD_FOLDER%\src" - nmake -f Makefile.w32 gifsicle.exe gifdiff.exe - gifsicle.exe --version - gifdiff.exe --version test: off gifsicle-1.96/.github/000077500000000000000000000000001475770763400146225ustar00rootroot00000000000000gifsicle-1.96/.github/workflows/000077500000000000000000000000001475770763400166575ustar00rootroot00000000000000gifsicle-1.96/.github/workflows/tests.yml000066400000000000000000000005661475770763400205530ustar00rootroot00000000000000name: Tests on: push: branches: [ master, github ] pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Configure and build run: | autoreconf -i ./configure - name: Make run: make - name: Run tests run: make check gifsicle-1.96/.gitignore000066400000000000000000000005021475770763400152470ustar00rootroot00000000000000*.d *.o .deps Makefile Makefile.in aclocal.m4 autom4te* config.cache config.h config.h.in config.h.in~ config.log config.status configure depcomp gifdiff gifsicle gifsicle-*.rpm gifsicle-*.tar.gz gifview include install-sh missing src/gifdiff src/gifsicle src/gifview stamp-h* ungifsicle-*.rpm ungifsicle-*.tar.gz compile gifsicle-1.96/.travis.yml000066400000000000000000000020271475770763400153740ustar00rootroot00000000000000language: c compiler: - gcc - clang env: global: # The next declaration is the encrypted COVERITY_SCAN_TOKEN, created # via the "travis encrypt" command using the project repo's public key - secure: "NEQUeZwTTJ+1Pdy2CpNte1nJLYRY2H8+dpvnGS2HP6U/Ok6ZQm4Zxic+ufhabuymbCV7Jvwwy6Gaj8+ObIDahhzAsiMRXSymnvFHELAnq7DiikJ2HqBvFhPzPI2/vDmEP37msGW1dloipLhdKMeuYYP++BpApnC6KVRVJDXg5JI=" matrix: include: - compiler: gcc - compiler: gcc env: CCARG=-std=c89 - compiler: gcc env: CCARG=-m32 addons: apt: packages: - gcc-multilib - libc6:i386 - compiler: clang script: ./configure || cat config.log && make check before_script: - export CC="$CC $CCARG" - ./bootstrap.sh sudo: false addons: coverity_scan: project: name: "kohler/gifsicle" description: "Build submitted via Travis CI" notification_email: ekohler@gmail.com build_command_prepend: "./configure; make clean" build_command: "make -j 4" branch_pattern: coverity_scan gifsicle-1.96/COPYING000066400000000000000000000432541475770763400143250ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 Lesser 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 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) 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 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 Lesser General Public License instead of this License. gifsicle-1.96/INSTALL000066400000000000000000000164461475770763400143260ustar00rootroot00000000000000Basic Installation ================== These are generic installation instructions. The `configure' shell script attempts to guess correct values for various system-dependent variables used during compilation. It uses those values to create a `Makefile' in each directory of the package. It may also create one or more `.h' files containing system-dependent definitions. Finally, it creates a shell script `config.status' that you can run in the future to recreate the current configuration, a file `config.cache' that saves the results of its tests to speed up reconfiguring, and a file `config.log' containing compiler output (useful mainly for debugging `configure'). If you need to do unusual things to compile the package, please try to figure out how `configure' could check whether to do them, and mail diffs or instructions to the address given in the `README' so they can be considered for the next release. If at some point `config.cache' contains results you don't want to keep, you may remove or edit it. The file `configure.in' is used to create `configure' by a program called `autoconf'. You only need `configure.in' if you want to change it or regenerate `configure' using a newer version of `autoconf'. The simplest way to compile this package is: 1. `cd' to the directory containing the package's source code and type `./configure' to configure the package for your system. If you're using `csh' on an old version of System V, you might need to type `sh ./configure' instead to prevent `csh' from trying to execute `configure' itself. Running `configure' takes awhile. While running, it prints some messages telling which features it is checking for. 2. Type `make' to compile the package. 3. Optionally, type `make check' to run any self-tests that come with the package. 4. Type `make install' to install the programs and any data files and documentation. 5. You can remove the program binaries and object files from the source code directory by typing `make clean'. To also remove the files that `configure' created (so you can compile the package for a different kind of computer), type `make distclean'. There is also a `make maintainer-clean' target, but that is intended mainly for the package's developers. If you use it, you may have to get all sorts of other programs in order to regenerate files that came with the distribution. Compilers and Options ===================== Some systems require unusual options for compilation or linking that the `configure' script does not know about. You can give `configure' initial values for variables by setting them in the environment. Using a Bourne-compatible shell, you can do that on the command line like this: CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure Or on systems that have the `env' program, you can do it like this: env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure Compiling For Multiple Architectures ==================================== You can compile the package for more than one kind of computer at the same time, by placing the object files for each architecture in their own directory. To do this, you must use a version of `make' that supports the `VPATH' variable, such as GNU `make'. `cd' to the directory where you want the object files and executables to go and run the `configure' script. `configure' automatically checks for the source code in the directory that `configure' is in and in `..'. If you have to use a `make' that does not supports the `VPATH' variable, you have to compile the package for one architecture at a time in the source code directory. After you have installed the package for one architecture, use `make distclean' before reconfiguring for another architecture. Installation Names ================== By default, `make install' will install the package's files in `/usr/local/bin', `/usr/local/man', etc. You can specify an installation prefix other than `/usr/local' by giving `configure' the option `--prefix=PATH'. You can specify separate installation prefixes for architecture-specific files and architecture-independent files. If you give `configure' the option `--exec-prefix=PATH', the package will use PATH as the prefix for installing programs and libraries. Documentation and other data files will still use the regular prefix. If the package supports it, you can cause programs to be installed with an extra prefix or suffix on their names by giving `configure' the option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. Optional Features ================= Some packages pay attention to `--enable-FEATURE' options to `configure', where FEATURE indicates an optional part of the package. They may also pay attention to `--with-PACKAGE' options, where PACKAGE is something like `gnu-as' or `x' (for the X Window System). The `README' should mention any `--enable-' and `--with-' options that the package recognizes. For packages that use the X Window System, `configure' can usually find the X include and library files automatically, but if it doesn't, you can use the `configure' options `--x-includes=DIR' and `--x-libraries=DIR' to specify their locations. Specifying the System Type ========================== There may be some features `configure' can not figure out automatically, but needs to determine by the type of host the package will run on. Usually `configure' can figure that out, but if it prints a message saying it can not guess the host type, give it the `--host=TYPE' option. TYPE can either be a short name for the system type, such as `sun4', or a canonical name with three fields: CPU-COMPANY-SYSTEM See the file `config.sub' for the possible values of each field. If `config.sub' isn't included in this package, then this package doesn't need to know the host type. If you are building compiler tools for cross-compiling, you can also use the `--target=TYPE' option to select the type of system they will produce code for and the `--build=TYPE' option to select the type of system on which you are compiling the package. Sharing Defaults ================ If you want to set default values for `configure' scripts to share, you can create a site shell script called `config.site' that gives default values for variables like `CC', `cache_file', and `prefix'. `configure' looks for `PREFIX/share/config.site' if it exists, then `PREFIX/etc/config.site' if it exists. Or, you can set the `CONFIG_SITE' environment variable to the location of the site script. A warning: not all `configure' scripts look for a site script. Operation Controls ================== `configure' recognizes the following options to control how it operates. `--cache-file=FILE' Use and save the results of the tests in FILE instead of `./config.cache'. Set FILE to `/dev/null' to disable caching, for debugging `configure'. `--help' Print a summary of the options to `configure', and exit. `--quiet' `--silent' `-q' Do not print messages saying which checks are being made. `--srcdir=DIR' Look for the package's source code in directory DIR. Usually `configure' can determine that directory automatically. `--version' Print the version of Autoconf used to generate the `configure' script, and exit. `configure' also accepts some other, not widely useful, options. gifsicle-1.96/Makefile.am000066400000000000000000000030001475770763400153070ustar00rootroot00000000000000## Process this file with automake to produce Makefile.in AUTOMAKE_OPTIONS = foreign SUBDIRS = src man_MANS = gifsicle.1 @OTHERMANS@ EXTRA_DIST = COPYING README.md NEWS.md \ include/lcdf/clp.h include/lcdf/inttypes.h \ include/lcdfgif/gif.h include/lcdfgif/gifx.h \ gifsicle.1 gifview.1 gifdiff.1 logo.gif logo1.gif \ test/testie \ test/001-transexpand.testie \ test/002-cropresize.testie \ test/003-bgopt.testie \ test/004-careful.testie \ test/005-resize.testie \ test/006-unoptdisposal.testie \ test/007-alltransp.testie \ test/008-resizemix.testie \ test/009-zerowidth.testie \ test/010-warnings.testie \ test/011-resizemix.testie gifsicle: config.h @cd src && $(MAKE) gifsicle gifdiff: config.h @cd src && $(MAKE) gifdiff gifview: config.h @cd src && $(MAKE) gifview srclinks: cd $(top_srcdir); sh ./sourcecheckout.sh versionize: perl -pi -e "s/^\\.ds V.*/.ds V $(VERSION)/;" $(top_srcdir)/gifsicle.1 $(top_srcdir)/gifview.1 $(top_srcdir)/gifdiff.1 perl -pi -e "s/gifsicle [\d.]+/gifsicle $(VERSION)/; s/VERSION \"[\w.]+/VERSION \"$(VERSION)/;" $(top_srcdir)/src/win32cfg.h dist-ungif: dist $(AMTAR) xzf gifsicle-$(VERSION).tar.gz @rm gifsicle-$(VERSION)/src/gifwrite.c rm -rf ungifsicle-$(VERSION) mv gifsicle-$(VERSION) ungifsicle-$(VERSION) GZIP=$(GZIP_ENV) $(AMTAR) chozf ungifsicle-$(VERSION).tar.gz ungifsicle-$(VERSION) rm -rf ungifsicle-$(VERSION) check: all $(top_srcdir)/test/testie -p $(top_builddir)/src $(top_srcdir)/test .PHONY: srclinks versionize rpm dist-ungif rpm-ungif check gifsicle-1.96/NEWS.md000066400000000000000000001103101475770763400143540ustar00rootroot00000000000000Gifsicle NEWS ============= ## Version 1.96 – 26.Feb.2025 * `--lossy` measures color errors uses the currently selected color space (specified by `--gamma`, which defaults to sRGB). It also uses a different algorithm for computing color differences. As a result, the `--lossy=N` option behaves differently in version 1.96 than in previous versions. A given value of `N` may compress less than you expect. The closest analogue to previous versions will be obtained with `--lossy=N --gamma=1`. * Improve handling of GIFs with images positioned far outside the logical screen. This can greatly reduce memory usage. * Add preliminary support for the Oklab color space (`--gamma=oklab`). * Add support for `--dither=atkinson` (daria@darkspot.net). * Add support for `--use-exact-colormap`. * Fix some bugs. ## Version 1.95 – 19.Feb.2024 * Fix some bugs. ## Version 1.94 – 23.Jun.2023 * Fix some bugs. ## Version 1.93 – 30.Jun.2021 * Fix security bug on certain resize operations with `--resize-method=box`. * Fix problems with colormapless GIFs. ## Version 1.92 – 18.Apr.2019 * Add `--lossy` option from Kornel Lipiński. * Remove an assertion failure possible with `--conserve-memory` + `--colors` + `--careful`. ## Version 1.91 – 5.Jan.2018 * Several security bug fixes with malicious GIFs. ## Version 1.90 – 14.Aug.2017 * Kill a use-after-free error reported by @junxzm1990. ## Version 1.89 - 11.Jul.2017 * Add SIMD support for resizing. When enabled this improves resize performance enormously for complex resize methods. * Add thread support for resizing. `-j[NTHREADS]` tells gifsicle to use up to NTHREADS threads to resize an input image. There are several caveats---multiple threads can be only used on unoptimized images. Thanks to Github user @wilkesybear. * Quashed several crashes and undefined behaviors. Thanks to Github users including @pornel, @strazzere, and @b0b0505. * Minor bug fixes. ## Version 1.88 - 1.Jul.2015 * Fix bug where long comments were read incorrectly. Reported by kazarny. * Add --no-ignore-errors option. ## Version 1.87 - 9.Dec.2014 * Always optimize as if the background is transparent. This fixes some rare bugs reported by Lars Dieckow. * Fix --crop issue with must-be-preserved frames that are out of the crop window. ## Version 1.86 - 14.Oct.2014 * Further fix --rotate + --crop. ## Version 1.85 - 14.Oct.2014 * Greatly improve optimization time for images with many colors. * Add --no-extensions (with the s) and document those options more. * Fix bug in interaction of --resize and --rotate reported by Michał Ziemba. ## Version 1.84 - 29.Jun.2014 * Correct optimizer bug that affected GIFs with 65535 or more total colors. Reported by Jernej Simončič. ## Version 1.83 - 21.Apr.2014 * Correct bug in custom gamma values reported by Kornel Lesiński. * Update Windows build. ## Version 1.82 - 27.Mar.2014 * Correct bug in `mix` sampling method reported by Bryan Stillwell. ## Version 1.81 - 24.Mar.2014 * Correct bug in `mix` sampling method reported by Bryan Stillwell. ## Version 1.80 - 18.Mar.2014 * Bug fixes and improved error messages. ## Version 1.79 - 17.Mar.2014 * Major improvements in image scaling. Work sponsored by Tumblr via Mike Hurwitz. * Add new resize sampling methods `mix`, `box`, `catrom`, `mitchell`, `lanczos2`, and `lanczos3`, selectable by `--resize-method`. The `catrom` filter often gives good results; the slightly faster `mix` method (a bilinear interpolator) is now the default. These new sampling methods consider all of the image's input colors when shrinking the image, producing better, less noisy output for most images. * Add `--resize-colors`, which allows Gifsicle to enlarge the palette when resizing images. This is particularly important when shrinking images with small colormaps---e.g., shrinking a black-and-white image should probably introduce shades of gray. * Support extensions such as XMP4 in which extension packet boundaries matter. Reported by `ata4`. * Many bug fixes, especially to cropping. Thanks to Tumblr and to Bryan Stillwell, Tal Lev-Ami, "Marco," and others. ## Version 1.78 - 9.Dec.2013 * Correct an optimization bug introduced in 1.76. Reported by Tom Roostan. ## Version 1.77 - 26.Nov.2013 * Major improvements to color selection (important when reducing colormap size). Use gamma-corrected colors in selection and dithering; this makes image quality much better. Also, when reducing colors with dithering, prefer to select colors that dithering can't approximate. * Add ordered dithering modes, which avoid animation artifacts. The default ordered dithering mode (`--dither=ordered`) is a novel mode that combines some of the visual advantages of error diffusion with the artifact avoidance of ordered dithering. * Add halftone dithering (`--dither=halftone`). * gifview: Improved cache memory management for better animations. Collect memory for old frames based on an explicit --memory-limit (default 40MB). * gifview: Add `--fallback-delay` option, to specify a fallback delay for frames with delay 0. Thanks to Sung Pae. ## Version 1.76 - 20.Nov.2013 * Fix `-O2` crashes introduced with `--resize` improvements. Reported by Bryan Stillwell. ## Version 1.75 - 18.Nov.2013 * Improve `--careful` (fewer crashes). Reported by Bryan Stillwell. * Improve `-O2`: again, don't refuse to optimize images with local color tables. Reported by Bryan Stillwell. * Greatly improve `--dither` speed. ## Version 1.74 - 16.Nov.2013 * Improve `--resize` behavior: avoid animation artifacts due to different rounding decisions. Also speed it up. Reported by Bryan Stillwell. ## Version 1.73 - 15.Nov.2013 * Fix bug where `-O2` would refuse to optimize some images with local color tables, claiming that "more than 256 colors were required". What was really required is previous disposal. Reported by Bryan Stillwell. ## Version 1.72 - 9.Nov.2013 * Fix crash bugs on some combinations of `--crop` and `--resize` (prevalent on images whose first frame didn't cover the whole logical screen). Reported by Bryan Stillwell. ## Version 1.71 - 15.Jun.2013 * Avoid rounding errors in `--resize`. Reported by Paul Kane. * Report error when `-I` is combined with `-b`. Reported by Frank Dana. * Frame selections also apply in batch mode. Reported by LOU Yu Hong Leo. ## Version 1.70 - 31.Jan.2013 * Fix `--crop` bug introduced in version 1.68, visible in images containing local color tables. Bug reported by Alberto Nannini. ## Version 1.69 - 31.Jan.2013 * Minor bug fix release. ## Version 1.68 - 24.Nov.2012 * gifsicle: Alberto Nannini reported some images that are optimized beyond what Gifsicle can do. In fact, Gifsicle's GIF writer was limited enough that even when running without optimization, it seriously expanded the input images. Some improvements to Gifsicle's writing procedure avoid this problem; now `gifsicle IMAGE` will produce large results less often, and when it does generate larger results, they're larger by hundreds of bytes, not hundreds of thousands. However, due to restrictions in the current optimizer (and on my time), I was unable to improve `gifsicle -O3`'s handling of these images. The optimizer that produced the images must be doing something pretty clever. (Is it SuperGIF?) ## Version 1.67 - 5.May.2012 * gifsicle: Frame specifications like "#2-0" are allowed; they insert frames in reverse order. Feature request from Leon Arnott. * gifview: Add --min-delay option. ## Version 1.66 - 2.Apr.2012 * gifsicle: Add -Okeep-empty for Gerald Johanson. ## Version 1.65 - 2.Apr.2012 * gifsicle: Several users (Kornel Lesiński and others) reported "bugs" with Gifsicle-optimized images and Mac programs like Safari. The bug is in Safari, but add --careful to work around it. * Improve -O3 a bit (although for some images, the new -O3 is bigger than the old). ## Version 1.64 - 22.Nov.2011 * gifsicle: Add --resize-fit options. Tom Glenne request. ## Version 1.63 - 17.Jul.2011 * gifsicle: Avoid crash on frame selections where frames are repeated. Werner Lemberg report. ## Version 1.62 - 4.Apr.2011 * gifsicle: -O3 optimization level tries even harder, so that now gifsicle -O3 should never produce larger results than gifsicle-1.60 -O2. Jernej Simončič report. ## Version 1.61 - 25.Feb.2011 * gifsicle: Add new -O3 optimization level, which applies several transparency heuristics and picks the one with the best result. David Jamroga pointed out a file that gifsicle made larger; -O3 shrinks it. * gifsicle: Correct some optimizer bugs introduced in 1.59 that could lead to visually different output. * gifdiff: Add -w/--ignore-redundancy option. * gifview: Correct crash bug. * gifsicle/gifdiff: Correct occasionally odd error messages. ## Version 1.60 - 12.Apr.2010 * GIF reading library: Correct error that could corrupt the reading of certain large images. Reported by Jernej Simončič. ## Version 1.59 - 11.Mar.2010 * gifsicle -O2: Optimize away entirely-transparent frames when possible. Requested by Gerald Johanson. ## Version 1.58 - 14.Jan.2010 * gifsicle: Fix optimizer bug reported by Dion Mendel. ## Version 1.57 - 11.Nov.2009 * gifsicle: Don't throw away totally-cropped frames with 0 delay, since most browsers treat 0-delay frames as 100ms-delay frames. Reported by Calle Kabo. ## Version 1.56 - 18.Oct.2009 * gifsicle: Fix --crop-transparency for animated images; previous versions could lose frames. Problem reported by Daniel v. Dombrowski. * gifview: Make --disposal=background behavior look like Firefox. ## Version 1.55 - 3.Apr.2009 * gifsicle: Another optimize fix for --disposal previous. Reported by Gerald Johanson. ## Version 1.54 - 3.Apr.2009 * gifsicle, gifview: Fix several serious bugs with --disposal previous that could affect optimized, unoptimized, and displayed images. Reported by Gerald Johanson. ## Version 1.53 - 19.Mar.2009 * gifsicle: Frames in an unoptimized image will use disposal "none" when possible. This leads to fewer surprises later. ## Version 1.52 - 18.May.2008 * gifsicle: Fix bug introduced in 1.51: --crop-transparent works. Reported by dgdd wanadoo fr. ## Version 1.51 - 12.May.2008 * gifsicle: `--crop` preserves the logical screen when it can. Reported by Petio Tonev. ## Version 1.50 - 7.May.2008 * gifsicle, gifview: Refuse to read GIFs from the terminal. Requested by Robert Riebisch. ## Version 1.49 - 2.May.2008 * Just some Makefile updates requested by Robert Riebisch. ## Version 1.48 - 16.Mar.2007 * gifsicle: Avoid crash in `--crop-transparency` when an image's first frame is totally transparent. Reported by Gerald Johanson. ## Version 1.47 - 10.Mar.2007 * gifsicle: Improve `--nextfile` behavior. ## Version 1.46 - 9.Jan.2007 * gifsicle: Add `--nextfile` option, useful for scripts. Problem reported by Jason Young. ## Version 1.45 - 30.Dec.2006 * Do not compile gifview by default when X is not available. * gifview: Add `--title` option, based on patch supplied by Andres Tello Abrego. ## Version 1.44 - 3.Oct.2005 * gifview: Enforce a minimum delay of 0.002 sec. Requested by Dale Wiles. ## Version 1.43 - 8.Sep.2005 * '-I -b', and other similar combinations, causes a fatal error rather than putting information in an unexpected file. Reported by Michiel de Bondt. ## Version 1.42 - 8.Jan.2005 * Improve manual page. ## Version 1.41 - 19.Aug.2004 * Fix problems with 64-bit machines. Thanks to Dan Stahlke for pointing out the problem and providing a patch. ## Version 1.40 - 28.Aug.2003 * Fix longstanding bug where `--disposal=previous` was mistakenly equivalent to `--disposal=asis`. * Include Makefile.bcc, from Stephen Schnipsel. ## Version 1.39 - 11.Jul.2003 * Fix Makefile.w32 (oops). ## Version 1.38 - 26.Jun.2003 * Include Makefile.w32 (oops). ## Version 1.37 - 11.Feb.2003 * Fix bug where combining `--rotate-X` and `-O` options would cause a segmentation fault. Reported by Dan Lasley . * Rearrange source tree. ## Version 1.36 - 17.Nov.2002 * Fix subscript-out-of-range error in main.c reported by Andrea Suatoni. ## Version 1.35 - 14.Aug.2002 * Fixed bug where `--crop` could cause a segmentation fault, present since 1.32 or 1.33. Reported by Tom Schumm . ## Version 1.34 - 13.Aug.2002 * Fixed bug where combining `--crop` and `-O` options could corrupt output. Reported by Tom Schumm . ## Version 1.33 - 1.Aug.2002 * Be more careful about time while animating. In particular, prepare frames before they are needed, so that they can be displayed exactly when required. Problem reported by Walter Harms . * More warning fixes. ## Version 1.32 - 5.Jul.2002 * Add `--multifile` option handling concatenated GIF files. This is useful for scripts. For example, `gifsicle --multifile -` will merge all GIF files written to its standard input into a single animation. * More fixes for spurious background warnings. ## Version 1.31 - 17.Jun.2002 * Changed behavior of `--crop X,Y+WIDTHxHEIGHT` option when WIDTH or HEIGHT is negative. Previously, zero or negative WIDTH and HEIGHT referred to the image's entire width or height. Thus, the option `--crop 10,0+0x0` would always lead to an error, because the crop left position (10) plus the crop width (the image width) was 10 pixels beyond the image edge. The new behavior measures zero or negative WIDTH and HEIGHT relative to the image's bottom-right corner. * Changed background behavior. Hopefully the only user-visible effect will be fewer spurious warnings. * Fixed a bug that could corrupt output when optimizing images with `-O2` that had more than 256 colors. ## Version 1.30 - 27.Jul.2001 * Fixed bug in ungif code: Writing a large ungif could corrupt memory, leading eventually to bad output. This bug has been present since Gifsicle could write ungifs! Bad files and assistance provided by Jeff Brown . ## Version 1.29 - 22.May.2001 * Fixed an optimization bug that could produce bad images. Again, reported by Dale Wiles . It turns out that 1.26 was a bad release! I've reconstructed and run some regression tests; hopefully Dale and I have exhausted the bug stream. ## Version 1.28 - 13.May.2001 * `--colors` option still didn't work; it produced bad images. Again, thanks to Dale Wiles for reporting the bug. * Added explicit `--conserve-memory` option. ## Version 1.27 - 10.May.2001 * Fixed `--colors` option, which caused segmentation faults. Thanks to Dale Wiles for reporting the bug. ## Version 1.26 - 22.Apr.2001 * Added `--crop-transparency` option, which crops any transparent edges off the image. Requested by Gre7g Luterman . * Try to conserve memory in gifsicle when working with huge images (> 20 megabytes of uncompressed data). May make gifsicle slower if you actually had enough memory to deal with the uncompressed data. ## Version 1.25 - 13.Feb.2001 * Fixed gifview bug: If every frame in an animation had a small or zero delay, then gifview would previously enter a infinite loop and become noninteractive. Reported by Franc,ois Petitjean . ## Version 1.24 - 11.Feb.2001 * Delete `unoptimized_pixmaps` array when deleting a viewer in gifview. Reported by Franc,ois Petitjean . * Added `--resize-width` and `--resize-height` options. * Why is it that Frenchmen are always telling me to delete the Clp_Parser immediately before calling exit()? Don't they realize that exit() frees application memory just as well as free()? Are things different in France? More French, perhaps? ## Version 1.23 - 12.Dec.2000 * In Houston on a layover because of stupid Continental Airlines: allow GIFs without terminators. Problem reported by Matt Olech . ## Version 1.22 - 23.Nov.2000 * Handle time more carefully when displaying animations. Should result in more precise timings. Problem reported by Iris Baye . ## Version 1.21 - 9.Sep.2000 * Fixed `--careful -O2`, which could create bad GIFs when there were bad interactions with transparency. Bug reported by Manfred Schwarb . ## Version 1.20 - 23.Jun.2000 * Added `--careful` option, because some bad GIF implementations (Java, Internet Explorer) don't support Gifsicle's super-optimized, but legal, GIFs. Problem reported by Andrea Beiser . * Gifdiff will work on very large images (better memory usage). ## Version 1.19 - 25.Apr.2000 * Fixed memory corruption bug: `gifsicle --use-colormap=FILE.gif` would formerly cause a segmentation fault. Bug reported by Alan Watts . ## Version 1.18 - 3.Apr.2000 * Gifview now uses X server memory more parsimoniously. This should make it more robust, and nicer to the X server, on large animations. Requested by Vladimir Eltsov . Appears to solve bug reported by Vince McIntyre . * Gifview treats frame selection more sensibly. A frame selection just tells it which frame to start on. * Added a message to the manual page warning people to quote the `#` character in frame selections. The interactive bash shell, for example, interprets it as a comment. ## Version 1.17 - 15.Mar.2000 * Added `--resize _xH` and `--resize Wx_`. Requested by Edwin Piekart . * Changed behavior of `--no-logical-screen`. Now gifsicle will include all visible GIFs when calculating the size of the logical screen. * Got rid of spurious redundant-option warnings. ## Version 1.16.1 - 10.Sep.1999 * Gifview can put a GIF (or an animated GIF) on the root window; use `gifview -w root`. Requested by Roland Blais . ## Version 1.16 - 31.Aug.1999 * Optimization improvement: More colormap manipulation. Version 1.16 produces smaller GIFs than 1.15 in the large majority of cases (but not all). * Fixed memory corruption bug in quantization. Found and fixed by Steven Marthouse . ## Version 1.15 - 20.Aug.1999 * Bug fix: no more assertion failures when combining images that require local colormaps. * Fixed serious quantization bug introduced in 1.14: when reducing the number of colors, gifsicle would ignore a random portion of the colors in the old colormap. * Optimization bug fix: two adjacent identical frames will no longer crash the optimizer. Bug introduced in 1.14. That's two for two: two "speed improvements" in 1.14, two bugs introduced! * Bug fix: Exploding the standard input results in files named `#stdin#.NNN`, as the documentation claims, not `.NNN`. * Optimization improvement: the optimizer reorders colormaps in a slightly smarter way, which can result in smaller compression. * If a position option like `-p 0,5` is followed by a multi-frame GIF (without frame specifications), then the position option places the animation as a whole, not each of its individual frames. * An unrecognized nonnumeric frame specification is now treated as a file name, so you can say `gifsicle #stdin#.000`, for example. * Gifview improvements: Now even images with local color tables can be "unoptimized" or animated. * `--replace` now preserves the replaced frame's disposal as well as its delay. * `gifsicle -I -` reports "" as the input file name. ## Version 1.14.2 - 16.Aug.1999 * Fixed memory bug in Gif_DeleteImage (I freed a block of memory, then accessed it). I am a moron!! This bug affected Version 1.14.1 only. ## Version 1.14.1 - 9.Aug.1999 * Fixed configuration bug: you couldn't compile gifsicle from a different directory (it told you to try --enable-ungif). * Steven Marthouse helped fix `Makefile.w32` to enable wildcards in filenames under Windows. Requested by Mark Olesen . ## Version 1.14 - 2.Aug.1999 * Switched to an adaptive tree strategy for LZW GIF compression, which is faster than the old hashing strategy. This method is the brainchild of Hans Dinsen-Hansen . * Bug fix/optimization improvement: Some images would include a transparent index that was not a valid color (it was larger than the colormap). Gifsicle formerly ignored these transparent indices. It doesn't any longer; furthermore, it will generate those kinds of transparent index, which can mean smaller colormaps and smaller GIFs. * Speed improvements in optimization and colormap quantization. * Added `--no-warnings` (`-w`) option to inhibit warning messages. Suggested by Andrea Beiser . * `--info` output can now be sent to a file with `-o`, just as with any other kind of output. * More specific error messages on invalid GIFs. ## Version 1.13 - 16.Jun.1999 * Added optional support for run-length-encoded GIFs to avoid patent problems. Run-length encoding idea based on code by Hutchinson Avenue Software Corporation found in Thomas Boutell's gd library . * `gifsicle --explode` now generates filenames of the form `file.000 .. file.100` rather than `file.0 .. file.100`, so you can use shell globbing like `file.*` and automatically get the right order. * Added `--background` option to gifview, so you can set the color used for transparent pixels. Suggested by Art Blair . * Gifdiff is built by default on Unix. ## Version 1.12 - 25.Mar.1999 * Added `--window` option to gifview, which lets it display a GIF in an arbitrary X window. Thanks to Larry Smith for patches. * Added `--install-colormap` to gifview. Suggested by Yair Lenga . * Gifsicle now exits with status 1 if there were any errors, status 0 otherwise. Before, it exited with status 1 only if there was a fatal error, or nothing was successfully output. The new behavior seems more useful. Problem reported by David Kelly . ## Version 1.11.2 - 10.Mar.1999 * `gifsicle -U` will now unoptimize 1-image GIFs as well as animations. ## Version 1.11.1 - 24.Jan.1999 * Fixed core dump on corrupted GIF files (in particular, bad extensions). Problem reported by tenthumbs . ## Version 1.11 - 22.Jan.1999 * Added `--scale XFACTORxYFACTOR` to complement `--resize`. * Made `--resize` work better on animations (no more off-by-one errors). * Fixed bug in `--use-colormap`: If transparency was added to the new colormap, the transparent color index could later be used (incorrectly) as a real image color. This was particularly common if the image was dithered. * `--dither` now tries to mitigate "jumping dither" animation artifacts (where adjacent frames in an animation have different dithering patterns, so the animation shifts). It does an OK job. * Improvements to `--color-method=blend-diversity`. ## Version 1.10 - 6.Jan.1999 * Gifview is built by default. * No other changes from 1.10b1: Emil said it worked on Windows. ## Version 1.10b1 - 31.Dec.1998 * The two malloc packages that come with Gifsicle don't #define `malloc` or `realloc`; they #define `xmalloc` and `xrealloc`. * Changes to Windows port from Emil Mikulic . ## Version 1.9.2 - 28.Dec.1998 * Gifsicle compiles out of the box on Windows! Port maintained by Emil Mikulic . * Moved to config.h-based configuration to simplify GIF library and Windows port. * Some CLP improvements. ## Version 1.9.1 - 16.Dec.1998 * If a reduced-colormap image needs a special transparent color, gifsicle will now try to keep the same transparent color value as the input GIF. * Cropping an image now automatically recalculates the output logical screen, rather than retaining the logical screen of the uncropped image. * Improvement to colormap modification behavior: If it looks like a color reserved for transparency will be necessary, gifsicle will reserve a slot for it. This will reduce the likelihood that you ask for a colormap of size 64, say, and get one of size 128 (because gifsicle added a slot for transparency). ## Version 1.9 - 14.Dec.1998 * `--no-background` is now the same as `--background=0` (set the background to pixel 0) instead of `--same-background`. ## Version 1.9b3 - 10.Dec.1998 * Fixed a serious bug in merge.c introduced in 1.8: Merging several images with global colormaps could corrupt the output colors. * Fixed a serious bug in optimize.c introduced in 1.9b1: Optimizing GIFs which required local color tables could crash the program due to a buffer overrun. (I was adding the background to the output colormap without making sure there was room for it.) * Fixed an optimizer bug which could result in incorrect animations: background disposal was not handled correctly. * The optimizer now generates `--disposal=none` instead of `--disposal=asis` in certain situations, which can result in smaller animations. Suggestion by Markus F.X.J. Oberhumer . * GIF library changes: cleaner, somewhat faster GIF reading code inspired by Patrick J. Naughton , whose code is distributed with XV. ## Version 1.9b2 - 9.Dec.1998 * Fixed `--logical-screen` and `--no-logical-screen`, which had no effect in 1.9b1. In a stunning turn of events, this problem was reported by Markus F.X.J. Oberhumer . ## Version 1.9b1 - 5.Dec.1998 * Added `--resize WxH` option, prompted by code from Christian Kumpf . * Made `--change-color PIXELINDEX color` work, and `--change-color color PIXELINDEX' illegal. * Many behavior fixes relating to background and transparency. Gifsicle would tend to create GIFs which looked as expected, but lost information internally; for example, a color value which was only used for transparency would be changed to black, no matter what the input color was. In some cases, gifsicle simply reports a warning now where it ignored information before. Problems reported by Markus F.X.J. Oberhumer . * Better error messages are now given on redundant, ambiguous, or useless command-line options. * Options that affect GIFs on output (such as `--output`, `--optimize`) now behave more like other options: you should put them before the files they affect. (You may still put them at the end of the argument list if they should affect the last output file.) This should only affect people who used batch or explode mode in complex ways. * Gifsicle will not write a GIF to stdout if it's connected to a terminal. ## Version 1.8 - 3.Dec.1998 * Fixed strange behavior when changing transparency: a new black entry was added to the colormap in almost all situations. Problem reported by Markus F.X.J. Oberhumer . * Fixed `--transform-colormap` bug: commands like "thing 2" (ending in an integer) will work now. * Small fixes (in a gifsicle pipeline, error messages won't be interleaved). ## Version 1.7.1 - 2.Dec.1998 * Added configuration check for `strerror`. Problem reported by Mario Gallotta . * Added colormap canonicalization: If you pipe a GIF through `gifsicle -O` or `-O2`, the output colormaps will be arranged in a predictable order. Feature suggested by Markus F.X.J. Oberhumer . ## Version 1.7 - 28.Nov.1998 * Added `--use-colormap=gray` and `--use-colormap=bw`. Idea and some code thanks to Christian Kumpf . * Added `--transform-colormap`, which allows you to plug programs into gifsicle that arbitrarily change GIF files' colormaps. * All `--change-color` options now happen simultaneously, so you can safely swap two colors with `--change-color C1 C2 --change-color C2 C1`. * Colormap modifications are slightly more powerful (they don't reserve a color for transparency if they don't need one, and if a subset of the modified colormap is used, only that subset is output). * Fixed `--use-colormap` bug: the last color in a used colormap would be ignored (it was reserved for transparency). Symptom: if you put a partially transparent image into the Web-safe palette, the result would never contain white. * When changing to a grayscale colormap, gifsicle now uses luminance difference to find the closest color. This gives better results for `--use-colormap=gray` and the like. * Added Introduction and Concept Index sections to the gifsicle manpage. * The gifsicle package now uses automake. ## Version 1.6 - 23.Nov.1998 * A frame extracted from the middle of an animation will now have the animation's screen size rather than 640x480. Problem reported by Mr. Moose . ## Version 1.5 - 27.Sep.1998 * `--help` now prints on stdout, as according to the GNU standards. * Changes to support Win32 port thanks to Emil Mikulic . * Makefiles: Added `make uninstall` target, enabled `./configure`'s program name transformations, made VPATH builds possible. ## Version 1.4.1 - 16.Sep.1998 * Fixed `--unoptimize` bug: a frame's transparency could disappear under rare circumstances. Bug reported by Rodney Brown ## Version 1.4 - 12.Sep.1998 * Added `--extension` and `--app-extension`. ## Version 1.3.4 - 7.Sep.1998 * More configuration changes. * Fixed bug in gifview `--geometry` option processing: `--geometry -0-0` wasn't recognized. ## Version 1.3.2 - 5.Sep.1998 * Fixed configuration bugs reported by Alexander Mai (OS/2 build didn't work). ## Version 1.3.1 - 4.Sep.1998 * Fixed configuration bug: int32_t could be improperly redefined. Reported by Anne "Idiot" Dudfield and Dax Kelson . ## Version 1.3 - 3.Sep.1998 * Added `--flip-*` and `--rotate-*` options. * Fixed rare bug in GIF writing code: the last pixel in a frame could previously become corrupted. ## Version 1.3b1 - 2.Aug.1998 * Optimization has been completely overhauled. All optimization functions are in a separate file, optimize.c. Some bugs have been caught. Optimization is now quite powerful, and rarely expands any input file. (An expansion of 1-30 bytes usually means that the input file cheated by leaving off 'end-of-image' codes, which the standard says must be used.) People interested in making optimization even better should contact me, or just go ahead and hack the code. Thanks again to David Hedbor for providing test cases. Specific changes: - The optimizer can handle any input file, even one with local colormaps or more than 256 colors. If there are more than 256 colors, an optimal subset is placed in the global colormap. - The global colormap is reordered so that some images can use a small initial portion of it, which lets them compress smaller. - Bug fix: Images relying on background disposal are optimized correctly. - `-O2` never does worse than `-O1`. - Made the `-O2` heuristic better. Some other small changes and improvements here and there. - The optimizer has been pretty extensively tested using gifdiff. * New program: gifdiff compares two GIFs for identical appearance. It is not built unless you give the `--enable-gifdiff` option to `./configure`. It's probably not useful unless you're testing a GIF optimizer. * Robustness fixes in GIF library. ## Version 1.2.1 - 8.Jun.1998 * `--info` now sends information to standard output. `--info --info` still sends information to standard error. * `--transparent` didn't work. It does now. * Added `--background` option. Fixed background processing so that an input background is reflected in the output GIF. * Re-fixed small bug with the interaction between per-frame options and frame selections. ## Version 1.2 - 28.May.1998 * Fixed small bug with the interaction between per-frame options (`--name`, `--comment`) and frame selections. * Fixed bad bug in merge: If a global colormap from the source needed to be dumped into a local colormap in the destination, the pixel map was wrong, resulting in bad colors in the destination. * Fixed memory leaks. Changed memory usage pattern in gifsicle to release source image memory as soon as possible, resulting in a better memory profile. Both programs now keep images in compressed form for better performance. * Fixed bugs in gifread.c that corrupted memory when uncompressing some bad images. * Added debugging malloc library (`./configure --enable-dmalloc` to use it). * Added `--no-delay` and `--no-disposal` options, which should have been there all along. * `--crop` will not generate an image with 0 width or height, even if a frame is cropped out of existence. * Other performance improvements. ## Version 1.2b6 - 25.May.1998 * License clarification in README. * GIF comments are now printed out more carefully in `--info`: binary characters appear as backslash escapes. (Literal backslashes are printed as `\\`.) * New `--extension-info/--xinfo` option. * Removed spurious warning about local colormaps when a `--colors` option is in effect. * Replacing a named frame with another image now keeps the name by default. * Many changes to gifview. Added `--animate`, `--display`, `--geometry`, `--name`, `--help` options, keystrokes, multiple GIFs on the command line, and a manual page. ## Version 1.2b5 - 12.May.1998 * Longstanding bug fix in gifunopt.c: an interlaced replacement frame caused strange behavior when later optimizing, as the underlying data wasn't deinterlaced. * Further improvements in optimizer; specifically, `-O2` works better now -- it uses actual image data to look for an unused pixel value when there's no transparency specified. ## Version 1.2b4 - 12.May.1998 * Improved `--optimize`'s performance on images where successive frames had different transparent color indexes. ## Version 1.2b3 - 11.May.1998 * Fixed bugs in `--unoptimize`: it would try to unoptimize images with local color tables and some transparency manipulations could cause a silent failure (example: 2 colors; frame 0 = [`*11`], frame 1 = [`*0*`], where `*` is a transparent pixel. Unoptimized, frame 1 should contain [`*01`] -- but this can't be expressed with only 2 colors.) * Greatly improved `--optimize`'s performance on some images by changing the way frames were switched to `--disposal=background`. Problem noticed by David Hedbor * Documentation improvements. * Unrecognized extensions are now passed through without change. The `--no-extensions` option (currently undocumented) removes all unrecognized extensions. * Bug fix in gifview: frame selections now prevent display of entire GIF. ## Version 1.2b2 - 9.May.1998 * Fixed bad dumb bug in gif.h: lack of parens around macro argument * Changed heuristic for diversity algorithm: never blend if there are 3 colors or less * Some optimizations to Floyd-Steinberg code ## Version 1.2b1 - 8.May.1998 * Added colormap options: `--colors`, `--color-method`, `--dither`, and `--use-colormap`. All this code is in quantize.c. * Renamed `--color-change` to `--change-color`. ## Version 1.1.2 * Added `--color-change` option. * Added negative frame numbers. `#-N` no longer means `#0-N`; it means the `N`th frame from the end (where `#-1` is the last frame). You can use negative frame numbers in ranges as well (e.g., `#-5--1`). ## Version 1.1.1 - 22.Nov.1997 * Fixed bug in CLP which segmentation-faulted on `-` arguments. ## Version 1.1 - 20.Nov.1997 * Added `--crop` option. * Changed usage behavior on bad command lines. ## Version 1.0 - 21.Jun.1997 * Added `--output/-o` option to specify output filename. * Slight behavior change: Now, if you replace a frame (say frame #2), its delay value will be preserved in the replacement frame, unless you give an explicit delay. (Before, the replacement frame's delay would be used, which was probably 0.) ## Version 0.91 - 15.Jun.1997 * Bug fix: `--optimize` didn't handle the bottom row of an image's changed area correctly. This could lead to incorrect animations. Specific change: merge.c line 744: was y < fbt, now y <= fbt. * Added `-S` as synonym for `--logical-screen`. ## Version 0.9 - 12.Jun.1997 * First public release. gifsicle-1.96/README.md000066400000000000000000000110321475770763400145360ustar00rootroot00000000000000![gifsicle-logo](https://raw.githubusercontent.com/kohler/gifsicle/master/logo.gif) Gifsicle [![Build Status](https://github.com/kohler/gifsicle/actions/workflows/tests.yml/badge.svg)](https://github.com/kohler/gifsicle/actions/workflows/tests.yml) ======== Gifsicle manipulates GIF image files. Depending on command line options, it can merge several GIFs into a GIF animation; explode an animation into its component frames; change individual frames in an animation; turn interlacing on and off; add transparency; add delays, disposals, and looping to animations; add and remove comments; flip and rotate; optimize animations for space; change images' colormaps; and other things. Gifview, a companion program, displays GIF images and animations on an X display. It can display multi-frame GIFs either as slideshows, displaying one frame at a time, or as real-time animations. Gifdiff, another companion program, checks two GIF files for identical visual appearance. This is probably most useful for testing GIF-manipulating software. Each of these programs has a manpage, `PROGRAMNAME.1`. The Gifsicle package comes with NO WARRANTY, express or implied, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose. See `NEWS.md` in this directory for changes in recent versions. The Gifsicle home page is: http://www.lcdf.org/gifsicle/ Building Gifsicle on UNIX ------------------------- Type `./configure`, then `make`. If `./configure` does not exist (you downloaded from Github), run `autoreconf -i` first. `./configure` accepts the usual options; see `INSTALL` for details. To build without gifview (for example, if you don't have X11), use `./configure --disable-gifview`. To build without gifdiff, use `./configure --disable-gifdiff`. `make install` will build and install Gifsicle and its manual page (under /usr/local by default). Building Gifsicle on Windows ---------------------------- To build Gifsicle on Windows using Visual C, change into the `src` directory and run nmake -f Makefile.w32 Gifview will not be built. The makefile is from Emil Mikulic with updates by Steven Marthouse . To build Gifsicle on Windows using Borland C++, change into the `src` directory and run nmake -f Makefile.bcc Stephen Schnipsel provided `Makefile.bcc`. You will need to edit one of these Makefiles to use a different compiler. You can edit it with any text editor (like Notepad). See the file for more information. Contact ------- If you have trouble building or running Gifsicle, try GitHub issues: https://github.com/kohler/gifsicle Eddie Kohler, ekohler@gmail.com http://www.read.seas.harvard.edu/~kohler/ Please note that I do not provide support for Windows. Copyright/License ----------------- All source code is Copyright (C) 1997-2025 Eddie Kohler. IF YOU PLAN TO USE GIFSICLE ONLY TO CREATE OR MODIFY GIF IMAGES, DON'T WORRY ABOUT THE REST OF THIS SECTION. Anyone can use Gifsicle however they wish; the license applies only to those who plan to copy, distribute, or alter its code. If you use Gifsicle for an organizational or commercial Web site, I would appreciate a link to the Gifsicle home page on any 'About This Server' page, but it's not required. This code is distributed under the GNU General Public License, Version 2 (and only Version 2). The GNU General Public License is available via the Web at or in the 'COPYING' file in this directory. The following alternative license may be used at your discretion. Permission is granted to copy, distribute, or alter Gifsicle, whole or in part, as long as source code copyright notices are kept intact, with the following restriction: Developers or distributors who plan to use Gifsicle code, whole or in part, in a product whose source code will not be made available to the end user -- more precisely, in a context which would violate the GPL -- MUST contact the author and obtain permission before doing so. Authors ------- This program is dedicated to the memory of Anne Dudfield, who named it. Anne was an incredible friend and ridiculous person. ANNIED!!!!!! Eddie Kohler \ http://www.read.seas.harvard.edu/~kohler/ \ He wrote it. David Hedbor \ Many bug reports and constructive whining about the optimizer. Emil Mikulic \ Win32 port help. Hans Dinsen-Hansen \ http://www.danbbs.dk/~dino/ \ Adaptive tree method for GIF writing. Kornel Lesiński \ `--lossy` option. gifsicle-1.96/bootstrap.sh000077500000000000000000000000301475770763400156270ustar00rootroot00000000000000#!/bin/sh autoreconf -i gifsicle-1.96/configure.ac000066400000000000000000000236771475770763400155670ustar00rootroot00000000000000dnl Process this file with autoconf to produce a configure script. AC_INIT([gifsicle],[1.96]) AC_PREREQ([2.69]) AC_CONFIG_SRCDIR([src/gifsicle.h]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_FILES([Makefile src/Makefile]) AM_INIT_AUTOMAKE ac_user_cc=${CC+y} AC_PROG_MAKE_SET AC_PROG_CC AC_PROG_CPP AC_C_CONST AC_C_INLINE AC_CHECK_HEADERS([sys/select.h sys/stat.h inttypes.h unistd.h]) build_gifview=maybe AC_ARG_ENABLE([gifview], [AS_HELP_STRING([--disable-gifview], [do not build gifview X11 viewer])], [if test "x$enableval" != xyes; then build_gifview=no; fi]) build_gifdiff=yes AC_ARG_ENABLE([gifdiff], [AS_HELP_STRING([--disable-gifdiff], [do not build gifdiff comparison utility])], [if test "x$enableval" != xyes; then build_gifdiff=no; fi]) AC_ARG_ENABLE([warnings], [AS_HELP_STRING([--disable-warnings], [compile without -W -Wall])], [:], [enable_warnings=yes]) if test x$enable_warnings = xyes; then CC="$CC -W -Wall" fi WERROR= AC_ARG_ENABLE([werror], [AS_HELP_STRING([--enable-werror], [compile with -Werror])], [if test "x$enableval" = xyes; then WERROR="-Werror"; fi]) AC_SUBST([WERROR]) use_dmalloc= AC_ARG_ENABLE([dmalloc], [AS_HELP_STRING([--enable-dmalloc], [build with debugging malloc library])], [if test "x$enableval" = xyes; then use_dmalloc=yes; fi]) ungif= AC_ARG_ENABLE([ungif], [AS_HELP_STRING([--enable-ungif], [build without LZW compression])], [if test "x$enableval" = xyes; then ungif=yes; fi]) dnl dnl Choose programs to build. Always build gifsicle dnl OTHERPROGRAMS="" OTHERMANS="" if test "x$build_gifview" = xyes -o "x$build_gifview" = xmaybe; then AC_PATH_XTRA if test "x$no_x" = xyes; then if test "x$build_gifview" = xyes; then AC_MSG_ERROR([ ****************************************************************************** Cannot find X, but you explicitly requested that gifview be built. You may need to install an X11 development package to get the X headers, supply a '--with-x' option, or simply '--disable-gifview'.]) else AC_MSG_WARN([Not compiling gifview since X is not available.]) build_gifview=no fi else build_gifview=yes fi fi if test "x$build_gifview" = xyes ; then OTHERPROGRAMS="$OTHERPROGRAMS gifview"'$(EXEEXT)' OTHERMANS="$OTHERMANS gifview.1" AC_CHECK_HEADERS([time.h sys/time.h]) AC_CACHE_CHECK([for gettimeofday prototype], [ac_cv_gettimeofday], [AC_COMPILE_IFELSE([AC_LANG_PROGRAM( [[#include #include ]], [[gettimeofday((void*) 0, (void*) 0)]])], [ac_cv_gettimeofday=2], [AC_COMPILE_IFELSE([AC_LANG_PROGRAM( [[#include #include ]], [[gettimeofday((void*) 0)]])], [ac_cv_gettimeofday=1], [ac_cv_gettimeofday=0])])]) AC_DEFINE_UNQUOTED([GETTIMEOFDAY_PROTO], [$ac_cv_gettimeofday], [Define to the number of arguments to gettimeofday.]) else AC_DEFINE_UNQUOTED(X_DISPLAY_MISSING, 1, [Define if X is not available.]) fi if test "x$build_gifdiff" = xyes ; then OTHERPROGRAMS="$OTHERPROGRAMS gifdiff"'$(EXEEXT)' OTHERMANS="$OTHERMANS gifdiff.1" fi AC_SUBST(OTHERPROGRAMS) AC_SUBST(OTHERMANS) dnl dnl Set up ungif support dnl if test "x$ungif" = xyes -o "x$GIFSICLE_UNGIF" = xyes ; then GIFWRITE_O=ungifwrt.o AC_DEFINE(GIF_UNGIF, 1, [Define if GIF LZW compression is off.]) elif test -r $srcdir/src/gifwrite.c ; then GIFWRITE_O=gifwrite.o else AC_MSG_ERROR([This version of gifsicle cannot create compressed GIFs. *** Either reconfigure with the '--enable-ungif' option or download the *** version with compression from http://www.lcdf.org/gifsicle/]) fi AC_SUBST(GIFWRITE_O) dnl dnl random or rand, strerror, strtoul, mkstemp, snprintf dnl AC_CHECK_FUNC([random], [random_func=random], [random_func=rand]) AC_DEFINE_UNQUOTED(RANDOM, ${random_func}, [Define to a function that returns a random number.]) AC_REPLACE_FUNCS([strerror]) AC_CHECK_FUNCS([strtoul mkstemp snprintf]) AC_SEARCH_LIBS([pow], [m], [AC_DEFINE([HAVE_POW], [1], [Define to 1 if you have the `pow' function.])]) AC_SEARCH_LIBS([cbrtf], [m], [AC_DEFINE([HAVE_CBRTF], [1], [Define to 1 if you have the `cbrtf' function.])]) dnl dnl integer types dnl AC_CHECK_HEADERS(inttypes.h, have_inttypes_h=yes, have_inttypes_h=no) AC_CHECK_HEADERS(sys/types.h, have_sys_types_h=yes, have_sys_types_h=no) if test $have_inttypes_h = no -a $have_sys_types_h = yes; then AC_CACHE_CHECK([for uintXX_t typedefs], ac_cv_uint_t, [AC_EGREP_HEADER(dnl changequote(<<,>>)<<(^|[^a-zA-Z_0-9])uint32_t[^a-zA-Z_0-9]>>changequote([,]), sys/types.h, ac_cv_uint_t=yes, ac_cv_uint_t=no)]) fi if test $have_inttypes_h = no -a $have_sys_types_h = yes -a "$ac_cv_uint_t" = no; then AC_CACHE_CHECK([for u_intXX_t typedefs], ac_cv_u_int_t, [AC_EGREP_HEADER(dnl changequote(<<,>>)<<(^|[^a-zA-Z_0-9])u_int32_t[^a-zA-Z_0-9]>>changequote([,]), sys/types.h, ac_cv_u_int_t=yes, ac_cv_u_int_t=no)]) fi if test $have_inttypes_h = yes -o "$ac_cv_uint_t" = yes; then : elif test "$ac_cv_u_int_t" = yes; then AC_DEFINE([HAVE_U_INT_TYPES], [1], [Define if you have u_intXX_t types but not uintXX_t types.]) fi AC_CHECK_TYPES([uintptr_t, int64_t, uint64_t], [], [], [#if HAVE_INTTYPES_H # include #endif #if HAVE_SYS_TYPES_H # include #endif ]) AC_CHECK_SIZEOF([float]) AC_CHECK_SIZEOF([unsigned int]) AC_CHECK_SIZEOF([unsigned long]) AC_CHECK_SIZEOF([void *]) dnl dnl vector types dnl AC_MSG_CHECKING([for usable vector_size vector types]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM( [AC_INCLUDES_DEFAULT [typedef float float4 __attribute__((vector_size (4 * sizeof(float))));]], [[float4 a; a[0] = a[1] = a[2] = a[3] = (float) rand(); a *= (float) 3; printf("%f\n", a[0]);]])], [AC_DEFINE([HAVE_VECTOR_SIZE_VECTOR_TYPES], 1, [Define to 1 if `vector_size' vector types are usable.]) ac_cv_vector_size_vector_types=yes], [ac_cv_vector_size_vector_types=no]) AC_MSG_RESULT([$ac_cv_vector_size_vector_types]) AC_MSG_CHECKING([for usable ext_vector_type vector types]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM( [AC_INCLUDES_DEFAULT [typedef float float4 __attribute__((ext_vector_type (4)));]], [[float4 a; a[0] = a[1] = a[2] = a[3] = (float) rand(); a *= (float) 3; printf("%f\n", a[0]);]])], [AC_DEFINE([HAVE_EXT_VECTOR_TYPE_VECTOR_TYPES], 1, [Define to 1 if `ext_vector_type' vector types are usable.]) ac_cv_ext_vector_type_vector_types=yes], [ac_cv_ext_vector_type_vector_types=no]) AC_MSG_RESULT([$ac_cv_ext_vector_type_vector_types]) AC_MSG_CHECKING([for __builtin_shufflevector]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM( [AC_INCLUDES_DEFAULT [typedef float float4 __attribute__((ext_vector_type (4)));]], [[float4 a; a[0] = a[1] = a[2] = a[3] = (float) rand(); a = __builtin_shufflevector(a, a, 3, 2, 1, 0); printf("%f\n", a[0]);]])], [AC_DEFINE([HAVE___BUILTIN_SHUFFLEVECTOR], 1, [Define to 1 if you have the `__builtin_shufflevector' function.]) ac_cv___builtin_shufflevector=yes], [ac_cv___builtin_shufflevector=no]) AC_MSG_RESULT([$ac_cv___builtin_shufflevector]) AC_ARG_ENABLE([simd], [AS_HELP_STRING([--disable-simd], [do not use SIMD instructions])], [], [enable_simd=maybe]) if test $ac_cv_vector_size_vector_types = no -a $ac_cv_ext_vector_type_vector_types = no; then if test x$enable_simd = xmaybe; then AC_MSG_WARN([ Not using SIMD since your compiler's support for SIMD types is inadequate. ]) elif test x$enable_simd != xno; then AC_MSG_ERROR([ ****************************************************************************** `--enable-simd' was supplied, but this compiler does not support SIMD types.]) fi enable_simd=no fi if test x"$enable_simd" != xno; then AC_DEFINE([HAVE_SIMD], [1], [Define to 1 if SIMD types should be used.]) fi dnl dnl threads dnl AC_SEARCH_LIBS([pthread_create], [pthread], [have_threads=yes], [have_threads=no]) AC_MSG_CHECKING([for __sync_add_and_fetch]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM( [AC_INCLUDES_DEFAULT extern volatile int x;], [[printf("%d", __sync_add_and_fetch(&x, 1));]])], [AC_DEFINE([HAVE___SYNC_ADD_AND_FETCH], 1, [Define to 1 if you have the `__sync_add_and_fetch' function.]) result=yes], [result=no]) AC_MSG_RESULT([$result]) AC_ARG_ENABLE([threads], [AS_HELP_STRING([--disable-threads], [do not include multithreading])], [], [enable_threads=maybe]) if test "x$have_threads" = xno -a "x$enable_threads" = xyes; then AC_MSG_ERROR([ ****************************************************************************** Threading support is not available on this platform.]) elif test "x$have_threads" = xyes -a "x$enable_threads" != xno; then AC_DEFINE([ENABLE_THREADS], [1], [Define to 1 if multithreading support is available.]) fi dnl dnl verbatim portions of the header dnl AC_DEFINE_UNQUOTED(PATHNAME_SEPARATOR, ['/'], [Pathname separator character ('/' on Unix).]) if false; then AC_DEFINE(OUTPUT_GIF_TO_TERMINAL, 1, [Define to write GIFs to stdout even when stdout is a terminal.]) fi AH_TOP([#ifndef GIFSICLE_CONFIG_H #define GIFSICLE_CONFIG_H]) AH_BOTTOM([#include #ifdef __cplusplus extern "C" { #endif /* Use the clean-failing malloc library in fmalloc.c. */ #define GIF_ALLOCATOR_DEFINED 1 #define Gif_Free free /* Prototype strerror if we don't have it. */ #ifndef HAVE_STRERROR char *strerror(int errno); #endif #ifdef __cplusplus } /* Get rid of a possible inline macro under C++. */ # define inline inline #endif /* Need _setmode under MS-DOS, to set stdin/stdout to binary mode */ /* Need _fsetmode under OS/2 for the same reason */ /* Windows has _isatty, not isatty */ /* Modern Windows has _snprintf, but we prefer snprintf */ #if defined(_MSDOS) || defined(_WIN32) || defined(__EMX__) || defined(__DJGPP__) # include # include # define isatty _isatty #endif #ifndef HAVE_SNPRINTF #define snprintf(s, n, ...) sprintf((s), __VA_ARGS__) #endif #endif /* GIFSICLE_CONFIG_H */]) dnl dnl Output dnl AC_OUTPUT gifsicle-1.96/gifdiff.1000066400000000000000000000031221475770763400147400ustar00rootroot00000000000000.\" -*- mode: nroff -*- .ds V 1.96 .ds E " \-\- .if t .ds E \(em .de Op .BR "\\$1" "\\$2" "\\$3" "\\$4" "\\$5" "\\$6" .. .de Sp .if n .sp .if t .sp 0.4 .. .de Es .Sp .RS 5 .nf .. .de Ee .fi .RE .PP .. .TH GIFDIFF 1 "11 July 2017" "Version \*V" .SH NAME gifdiff \- compares GIF images .SH SYNOPSIS .B gifdiff \%[options] GIF\-file\-1 GIF\-file\-2 ' .SH DESCRIPTION .B gifdiff compares two GIF files and determines if they appear identical. Differences that don't affect appearance (like colormap ordering or how much an animation is optimized) are not reported. .PP .B gifdiff prints details of any differences it finds. If the GIFs are the same, it prints nothing. It exits with status 0 if there were no differences, 1 if there were some differences, and 2 if there was trouble. ' .SH OPTIONS .PD 0 .TP 5 .Op \-\-brief ", " \-q ' Report only whether the GIFs differ, not the details of the differences. ' .Sp .TP 5 .Op \-\-ignore\-redundancy ", " \-w ' Do not report differences in the numbers of redundant frames (frames which do not change the displayed image). ' .Sp .TP 5 .Op \-\-ignore\-background ", " \-B ' Do not report differences in background colors. ' .Sp .TP 5 .Op \-\-help ' Print usage information and exit. ' .Sp .TP .Op \-\-version ' Print the version number and some quickie warranty information and exit. ' .PD ' .SH SEE ALSO .BR gifsicle (1) ' .SH BUGS Please email suggestions, additions, patches and bugs to ekohler@gmail.com. ' .SH AUTHORS .na Eddie Kohler, ekohler@gmail.com .br http://www.read.seas.harvard.edu/~kohler/ .PP http://www.lcdf.org/gifsicle/ .br The .B gifsicle home page. ' gifsicle-1.96/gifsicle.1000066400000000000000000001024641475770763400151400ustar00rootroot00000000000000.\" -*- mode: nroff -*- .AM .ds V 1.96 .ds E " \-\- .if t .ds E \(em .de Op .BR "\\$1" "\\$2" "\\$3" "\\$4" "\\$5" "\\$6" .. .de Oa .IR "\fB\\$1\& \|\fI\\$2" "\\$3" "\\$4" "\\$5" "\\$6" .. .de Qo .RB \(oq "\\$1" "\(cq\\$2" .. .de Qa .BI "\fR\(oq\fB\\$1" " \\$2" " \\$3" " \\$4" "\fR\(cq\\$5" .. .de Sp .if n .sp .if t .sp 0.4 .. .de Ix .TP 25 \\$1 .nh \\$2 .hy .. .de Es .Sp .RS 5 .nf .. .de Ee .fi .RE .PP .. .de Xs .RS 5 .nf .. .de Xe .fi .RE .. .TH GIFSICLE 1 "26 February 2025" "Version \*V" .SH NAME gifsicle \- manipulates GIF images and animations .SH SYNOPSIS .B gifsicle \%[options, frames, and filenames].\|.\|. ' .SH DESCRIPTION .B gifsicle creates, edits, manipulates, and prints information about GIF images and animations. .PP .B Gifsicle normally processes input GIF files according to its command line options and writes the result to the standard output. The .Op \-i option, for example, tells .B gifsicle to interlace its inputs: .Es \fBgifsicle \-i < pic.gif > interlaced-pic.gif\fR .Ee .PP By default, .B gifsicle combines two or more input files into a \(lqflipbook\(rq animation: .Es \fBgifsicle pic1.gif pic2.gif pic3.gif > animation.gif\fR .Ee Use options like .Op \-\-delay ", " \-\-loopcount ", and " \-\-optimize to tune your animations. .PP To modify GIF files in place, use the .Op \-\-batch option. With .Op \-\-batch , .B gifsicle will modify the files you specify instead of writing a new file to the standard output. To interlace all the GIFs in the current directory, you could say: .Es \fBgifsicle \-\-batch \-i *.gif .Ee .PP New users may want to skip to the Examples section at the end. ' .SH CONCEPT INDEX ' Concepts are on the left, relevant .B gifsicle options are on the right. ' .Sp .PD 0 .Ix "Animations, changing" "frame selections, frame changes, etc." .Ix "\ \ \ disposal" "\fB\-\-disposal\fP" .Ix "\ \ \ looping" "\fB\-\-loopcount\fP" .Ix "\ \ \ portions of" "frame selections" .Ix "\ \ \ smaller" "\fB\-\-optimize\fP, \fB\-\-colors\fP, \fB\-\-lossy\fP" .Ix "\ \ \ speed" "\fB\-\-delay\fP" .Ix "Bad output" "\fB\-\-careful\fP" .Ix "Background color" "\fB\-\-background\fP" .Ix "Colors, changing" "\fB\-\-change\-color\fP, \fB\-\-use\-colormap\fP, \fB\-\-dither\fP, \fB\-\-transform\-colormap\fP" .Ix "\ \ \ reducing number" "\fB\-\-colors\fP, \fB\-\-dither\fP, \fB\-\-gamma\fP" .Ix "Comments" "\fB\-\-comment\fP" .Ix "Extensions" "\fB\-\-extension\fP, \fB\-\-app\-extension\fP, \fB\-\-extension\-info\fP" .Ix "File size" "\fB\-\-optimize\fP, \fB\-\-unoptimize\fP, \fB\-\-colors\fP, \fB\-\-lossy\fP" .TP 30 Image transformations .Ix "\ \ \ cropping" "\fB\-\-crop\fP, \fB\-\-crop\-transparency\fP" .Ix "\ \ \ flipping" "\fB\-\-flip\-*\fP" .Ix "\ \ \ resizing" "\fB\-\-resize\fP, \fB\-\-scale\fP" .Ix "\ \ \ rotating" "\fB\-\-rotate\-*\fP" .Ix "Grayscale" "\fB\-\-use\-colormap\fP" .Ix "Interlacing" "\fB\-\-interlace\fP" .Ix "Positioning frames" "\fB\-\-position\fP" .Ix "Screen, logical" "\fB\-\-logical\-screen\fP" .Ix "Selecting frames" "frame selections (like \fB'#0'\fP)" .Ix "Transparency" "\fB\-\-transparent\fP" .Ix "Warnings" "\fB\-\-no\-warnings\fP" .PD ' .SH COMMAND LINE .BR gifsicle 's command line consists of GIF input files and options. Most options start with a dash (\-) or plus (+); frame selections, a kind of option, start with a number sign (#). Anything else is a GIF input file. .PP .B gifsicle reads and processes GIF input files in order. If no GIF input file is given, or you give the special filename \(oq\-\(cq, it reads from the standard input. .PP .B gifsicle exits with status 0 if there were no errors and status 1 otherwise. ' .SH OPTIONS Every option has a long form, .Qo \-\-long\-descriptive\-name . You don't need to type the whole long descriptive name, just enough to make it unambiguous. .PP Some options also have a short form, .Qo \-X . You can combine short options if they don't take arguments: .Qo \-IIb is the same as .Qo "\-I \-I \-b" . But be careful with options that do take arguments: .Qo \-cblah means .Qo "\-c \fRblah" , not .Qo "\-c \-b \-l \-a \-h" . .PP Many options also have a converse, .Qo \-\-no\-option , which turns off the option. You can turn off a short option .Qo \-X by saying .Qo \+X instead. ' .\" ----------------------------------------------------------------- .SS Mode Options Mode options tell .B gifsicle what kind of output to generate. There can be at most one, and it must precede any GIF inputs. .TP 5 .Op "\-\-merge" ", " "\-m" ' Combine all GIF inputs into one file with multiple frames and write that file to the standard output. This is the default mode. ' .TP .Op \-\-batch ", " \-b ' Modify each GIF input in place by reading and writing to the same filename. (GIFs read from the standard input are written to the standard output.) ' .TP .Op \-\-explode ", " \-e ' Create an output GIF for each frame of each input file. The output GIFs are named \(oqxxx.000\(cq, \(oqxxx.001\(cq, and so on, where \(oqxxx\(cq is the name of the input file (or whatever you specified with .Qo \-\-output ) and the numeric extension is the frame number. ' .TP .Op \-\-explode\-by\-name ", " \-E ' Same as .Op \-\-explode "," but write any named frames to files \(oqxxx.\fIname\fR\(cq instead of \(oqxxx.\fIframe-number\fR\(cq. Frames are named using the .Qo \-\-name option. ' .\" ----------------------------------------------------------------- .SS General Options General options control the information .B gifsicle prints and where it writes its output. The info options and .Op \-\-verbose can be turned off with .Qo \-\-no\-X . ' .Sp .PD 0 .TP 5 .Op \-\-info ", " \-I ' Print a human-readable description of each input GIF to the standard output, or whatever file you specify with .Op \-o . This option suppresses normal output, and cannot be combined with mode options like .Op \-\-batch . If you give two .Op \-\-info or .Op \-I options, however, information is printed to standard error, and normal output takes place as usual. ' .Sp .TP 5 .Op \-\-color\-info ", " \-\-cinfo ' Like .Op \%\-\-info , but also print information about input files' colormaps. ' .Sp .TP 5 .Op \-\-extension\-info ", " \-\-xinfo ' Like .Op \%\-\-info , but also print any unrecognized GIF extensions in a .BR hexdump (1)-like format. ' .Sp .TP 5 .Op \-\-size\-info ", " \-\-sinfo ' Like .Op \%\-\-info , but also print information about compressed image sizes. ' .Sp .TP 5 .Op \-\-help ", " \-h ' Print usage information and exit. ' .Sp .TP .Oa \-o file .TP .Oa \-\-output file ' Send output to .IR file . The special filename \(oq-\(cq means the standard output. ' .Sp .TP .Op \-\-verbose ", " \-V ' Print progress information (files read and written) to standard error. ' .Sp .TP .Op \-\-no\-warnings ", " \-w ' Suppress all warning messages. ' .Sp .TP .Op \-\-no\-ignore\-errors ' Exit with status 1 when encountering a very erroneous GIF. Default is to muddle on. ' .Sp .TP .Op \-\-version ' Print the version number and some short non-warranty information and exit. ' .Sp .PD 0 .TP 5 .Op \-\-careful ' Write slightly larger GIFs that avoid bugs in some other GIF implementations. Some Java and Internet Explorer versions cannot display the correct, minimal GIFs that Gifsicle produces. Use the .Op \-\-careful option if you are having problems with a particular image. ' .Sp .TP .Op \-\-conserve\-memory ' Conserve memory usage at the expense of processing time. This may be useful if you are processing large GIFs on a computer without very much memory. Or say .Op \-\-no\-conserve\-memory . ' .Sp .TP .Op \-\-nextfile ' Allow input files to contain multiple concatenated GIF images. If a filename appears multiple times on the command line, \fBgifsicle\fR will read a new image from the file each time. This option can help scripts avoid the need for temporary files. For example, to create an animated GIF with three frames with different delays, you might run "\fBgifsicle \-\-nextfile \-d10 \- \-d20 \- \-d30 \- > out.gif\fR" and write the three GIF images, in sequence, to \fBgifsicle\fR's standard input. ' .Sp .TP .Op \-\-multifile ' Like .Op \-\-nextfile , but read .I as many GIF images as possible from each file. This option is intended for scripts. For example, to merge an unknown number of GIF images into a single animation, run "\fBgifsicle \-\-multifile \- > out.gif\fR" and write the GIF images, in sequence, to \fBgifsicle\fR's standard input. Any frame selections apply only to the last file in the concatenation. ' .PD ' .\" ----------------------------------------------------------------- .SS Frame Selections A frame selection tells .B gifsicle which frames to use from the current input file. They are useful only for animations, as non-animated GIFs only have one frame. Here are the acceptable forms for frame specifications. .Sp .PD 0 .TP 13 .BI # num ' Select frame \fInum\fR. (The first frame is .Qo #0 . Negative numbers count backwards from the last frame, which is .Qo #-1 .) ' .TP 13 .BI # num1 \- num2 ' Select frames \fInum1\fR through \fInum2\fR. ' .TP 13 .BI # num1 \- ' Select frames \fInum1\fR through the last frame. ' .TP 13 .BI # name ' Select the frame named \fIname\fR. .PD .PP The \(oq#\(cq character has special meaning for many shells, so you generally need to quote it. .PP For example, .Xs \fBgifsicle happy.gif "#0"\fR .Xe uses the first frame from happy.gif; .Xs \fBgifsicle happy.gif "#0-2"\fR .Xe uses its first three frames; and .Xs \fBgifsicle happy.gif "#-1-0"\fR .Xe uses its frames in reverse order (starting from frame #-1\*Ethe last frame\*Eand ending at frame #0\*Ethe first). .PP The action performed with the selected frames depends on the current mode. In merge mode, only the selected frames are merged into the output GIF. In batch mode, only the selected frames are modified; other frames remain unchanged. In explode mode, only the selected frames are exploded into output GIFs. ' .\" ----------------------------------------------------------------- .SS Frame Change Options Frame change options insert new frames into an animation or replace or delete frames that already exist. Some things\*Efor example, changing one frame in an animation\*Eare difficult to express with frame selections, but easy with frame changes. ' .TP 5 .Oa \-\-delete frames " [" frames ".\|.\|.]" ' Delete .I frames from the input GIF. ' .TP .Oa \-\-insert\-before "frame other-GIFs" ' Insert .I other-GIFs before .I frame in the input GIF. ' .TP .Oa \-\-append "other-GIFs" ' Append .I other-GIFs to the input GIF. ' .TP .Oa \-\-replace "frames other-GIFs" ' Replace .I frames from the input GIF with .IR other-GIFs . ' .TP \fB\-\-done\fR ' Complete the current set of frame changes. ' .PP The .I frames arguments are frame selections (see above). These arguments always refer to frames from the .I original input GIF. So, if \(oqa.gif\(cq has 3 frames and \(oqb.gif\(cq has one, this command .Xs \fBgifsicle a.gif \-\-delete "#0" \-\-replace "#2" b.gif\fR .Xe will produce an output animation with 2 frames: \(oqa.gif\(cq frame 1, then \(oqb.gif\(cq. .PP The .I other-GIFs arguments are any number of GIF input files and frame selections. These images are combined in merge mode and added to the input GIF. The .I other-GIFs last until the next frame change option, so this command replaces the first frame of \(oqin.gif\(cq with the merge of \(oqa.gif\(cq and \(oqb.gif\(cq: .Xs \fBgifsicle \-b in.gif \-\-replace "#0" a.gif b.gif\fR .Xe .PP This command, however, replaces the first frame of \(oqin.gif\(cq with \(oqa.gif\(cq and then processes \(oqb.gif\(cq separately: .Xs \fBgifsicle \-b in.gif \-\-replace "#0" a.gif \-\-done b.gif\fR .Xe .PP Warning: You shouldn't use both frame selections and frame changes on the same input GIF. ' .\" ----------------------------------------------------------------- .SS Image Options Image options modify input images\*Eby changing their interlacing, transparency, and cropping, for example. Image options have three forms: .Qo \-\-X , .Qo \-\-no\-X , and .Qo \-\-same\-X . The .Qo \-\-X form selects a value for the feature, the .Qo \-\-no\-X form turns off the feature, and the .Qo \-\-same\-X form means that the feature's value is copied from each input. The default is always .Qo \-\-same\-X . For example, .Op \-\-background= """#0000FF""" sets the background color to blue, .Op \-\-no\-background turns the background color off (by setting it to 0), and .Op \-\-same\-background uses input images' existing background colors. You can give each option multiple times; for example, .Xs \fBgifsicle \-b \-O2 \-i a.gif \-\-same\-interlace b.gif c.gif\fR .Xe will make \(oqa.gif\(cq interlaced, but leave \(oqb.gif\(cq and \(oqc.gif\(cq interlaced only if they were already. ' .Sp .PD 0 .TP 5 .Oa \-B color .TP .Oa \-\-background color ' Set the output GIF's background to .IR color . The argument can have the same forms as in the .Op \-\-transparent option below. ' .Sp .TP .Oa \-\-crop x1 , y1 - x2\fR,\fIy2 .TP .Oa \-\-crop x1 , y1 + width\fRx\fIheight ' Crop the following input frames to a smaller rectangular area. The top-left corner of this rectangle is .RI ( x1 , y1 ); you can give either the lower-right corner, .RI ( x2 , y2 ), or the width and height of the rectangle. In the .IR x1 , y1 + width x height form, .I width and .I height can be zero or negative. A zero dimension means the cropping area goes to the edge of the image; a negative dimension brings the cropping area that many pixels back from the image edge. For example, .Op \-\-crop " 2,2+-2x-2" will shave 2 pixels off each side of the input image. Cropping happens before any rotation, flipping, resizing, or positioning. ' .Sp .TP .Op \-\-crop\-transparency ' Crop any transparent borders off the following input frames. This happens after any cropping due to the .Op \-\-crop option. It works on the raw input images; for example, any transparency options have not yet been applied. ' .Sp .TP .Op \-\-flip\-horizontal .TP .Op \-\-flip\-vertical ' Flip the following frames horizontally or vertically. ' .Sp .TP .Op \-i .TP .Op \-\-interlace ' Turn interlacing on. ' .Sp .TP .Oa \-S width x height .TP .Oa \-\-logical\-screen width x height ' Set the output logical screen to .IR width x height . .Op \-\-no\-logical\-screen sets the output logical screen large enough to include all output frames, while .Op \-\-same\-logical\-screen sets the output logical screen large enough to include all input logical screens. .Op \-\-screen is a synonym for .Op \-\-logical\-screen . ' .Sp .TP .Oa \-p x\fR,\fIy .TP .Oa \-\-position x\fR,\fIy ' Set the following frames' positions to .RI ( x , y ). .Op \-\-no\-position means .Op \-\-position " 0,0." Normally, .Oa \-\-position x\fR,\fIy places every succeeding frame exactly at \fIx\fR,\fIy\fR. However, if an entire animation is input, \fIx\fR,\fIy\fR is treated as the position for the animation. ' .Sp .TP .Op \-\-rotate\-90 .TP .Op \-\-rotate\-180 .TP .Op \-\-rotate\-270 ' Rotate the following frames by 90, 180, or 270 degrees. .Op \-\-no\-rotate turns off any rotation. ' .Sp .TP .Oa \-t color .TP .Oa \-\-transparent color ' Make .I color transparent in the following frames. .I Color can be a colormap index (0\-255), a hexadecimal color specification (like "#FF00FF" for magenta), or slash- or comma-separated red, green and blue values (each between 0 and 255). .PD ' .\" ----------------------------------------------------------------- .SS Extension Options Extension options add non-visual information to the output GIF. This includes names, comments, and generic extensions. ' .Sp .PD 0 .TP 5 .Oa \-\-app\-extension app\-name " " extension ' Add an application extension named .I app\-name and with the value .I extension to the output GIF. .Op \-\-no\-app\-extensions removes application extensions from the input images. ' .Sp .TP .Oa \-c text .TP .Oa \-\-comment text ' Add a comment, .IR text , to the output GIF. The comment will be placed before the next frame in the stream. .Op \-\-no\-comments removes comments from the input images. ' .Sp .TP .Oa \-\-extension number " " extension ' Add an extension numbered .I number and with the value .I extension to the output GIF. .I Number can be in decimal, octal, hex, or it can be a single character like \(oqn\(cq, whose ASCII value is used. .Op \-\-no\-extensions (or .Op +x ) removes extensions from the input images. ' .Sp .TP .Oa \-n text .TP .Oa \-\-name text ' Set the next frame's name to .IR text . This name is stored as an extension in the output GIF (extension number 0xCE, followed by the characters of the frame name). .Op \-\-no\-names removes name extensions from the input images. ' .PD ' .\" ----------------------------------------------------------------- .SS Animation Options Animation options apply to GIF animations, or to individual frames in GIF animations. As with image options, most animation options have three forms, .Qo \-\-X , .Qo \-\-no\-X , and .Qo \-\-same\-X , and you can give animation options multiple times; for example, .Xs \fBgifsicle \-b a.gif \-d50 "#0" "#1" \-d100 "#2" "#3"\fR .Xe sets the delays of frames 0 and 1 to 50, and frames 2 and 3 to 100. ' .Sp .PD 0 .TP 5 .Oa \-d time .TP .Oa \-\-delay time ' Set the delay between frames to .IR time in hundredths of a second. ' .Sp .TP .Oa \-D method .TP .Oa \-\-disposal method ' Set the disposal method for the following frames to .IR method . A frame's disposal method determines how a viewer should remove the frame when it's time to display the next. .I Method can be a number between 0 and 7 (although only 0 through 3 are generally meaningful), or one of these names: .BR none (leave the frame visible for future frames to build upon), .BR asis (same as "none"), .BR background " (or " bg ")" (replace the frame with the background), or .BR previous (replace the frame with the area from the previous displayed frame). .Op \-\-no\-disposal means .Op \-\-disposal = none . ' .Sp .TP .Op \-l "[\fIcount\fR]" .TP .Op \-\-loopcount "[=\fIcount\fR]" ' Set the Netscape loop extension to .IR count . .I Count is an integer, or .B forever to loop endlessly. If you supply a .Op \-\-loopcount option without specifying .IR count , Gifsicle will use .BR forever . .Op \-\-no\-loopcount (the default) turns off looping. .Sp Set the loop count to one less than the number of times you want the animation to run. An animation with .Op \-\-no\-loopcount will show every frame once; .Op \-\-loopcount =1 will loop once, thus showing every frame twice; and so forth. Note that .Op \-\-loopcount =0 is equivalent to .Op \-\-loopcount =forever, not .Op \-\-no\-loopcount . ' .Sp .TP .Op \-O "[\fIlevel\fR]" .TP .Op \-\-optimize "[=\fIlevel\fR]" ' Attempt to shrink the file sizes of GIF animations. .I Level determines how much optimization is done; higher levels take longer, but may have better results. There are currently three levels: .Sp .RS .TP 5 .Op \-O1 Store only the changed portion of each image. This is the default. .TP 5 .Op \-O2 Store only the changed portion of each image, and use transparency. .TP 5 .Op \-O3 Try several optimization methods (usually slower, sometimes better results). .Sp .PP Other optimization flags provide finer-grained control. .Sp .TP 5 .Op \-Okeep-empty Preserve empty transparent frames (they are dropped by default). .Sp .PP You may also be interested in other options for shrinking GIFs, such as .Op \-k , .Op \-\-lossy , and .Op \-\-no\-extensions . Note that .Op \-O does not guarantee to shrink file size, and in rare cases, even .Op \-O3 may actually enlarge file size. .RE ' .Sp .TP 5 .Oa \-U .TP .Oa \-\-unoptimize ' Unoptimize GIF animations into an easy-to-edit form. .Sp GIF animations are often optimized (see .Op \-\-optimize ) to make them smaller and faster to load, which unfortunately makes them difficult to edit. .Op \-\-unoptimize changes optimized input GIFs into unoptimized GIFs, where each frame is a faithful representation of what a user would see at that point in the animation. .RE ' .PD ' .\" ----------------------------------------------------------------- .SS Image Transformation Options ' Image transformation options apply to entire GIFs as they are read or written. They can be turned off with .Qo \-\-no\-option . ' .Sp .PD 0 .TP 5 .Oa \-\-resize width x height ' Resize the output GIF to the given .IR width " and " height . If .I width or .I height is an underscore \(oq_\(cq, that dimension is chosen so that the aspect ratio remains unchanged. Resizing happens after all input frames have been combined and before optimization. Resizing uses logical screen dimensions; if the input stream has an unusual logical screen (many GIF displayers ignore logical screens), you may want to provide .Op \-\-no\-logical\-screen (or .Op +S ) to reset it so .B gifsicle uses image dimensions instead. See also .Op \-\-resize\-method . ' .Sp .TP .Oa \-\-resize\-width width .TP .Oa \-\-resize\-height height ' Resize to a given width or height, preserving aspect ratio. Equivalent to .Oa \-\-resize width x_ or .Oa \-\-resize "" _x height . ' .Sp .TP .Oa \-\-resize\-fit width x height .TP .Oa \-\-resize\-touch width x height ' Resize the output GIF to fit a rectangle with dimensions .IR width x height . The aspect ratio remains unchanged. The .Op \-\-resize\-fit option only shrinks the image\*Eno resize is performed if the GIF already fits within the rectangle. The .Op \-\-resize\-touch option may shrink or grow the image; the output GIF will have width .IR width , height .IR height , or both. Either .I width or .I height may be an underscore \(oq_\(cq, which leaves that dimension unconstrained. ' .Sp .TP .Oa \-\-resize\-fit\-width width .TP .Oa \-\-resize\-fit\-height height .TP .Oa \-\-resize\-touch\-width width .TP .Oa \-\-resize\-touch\-height height ' Like .Oa \-\-resize\-fit and .Op \-\-resize\-touch , but constrains only one dimension. ' .Sp .TP .Oa \-\-resize\-geometry geometry ' Resize to a geometry specification as used in ImageMagick and GraphicsMagick. .I Geometry should match .RI \(oq width x height [%][!][^][<][>]\(cq, where .IR width " and " height are integers (or underscores, \(oq_\(cq, which leaves that dimension unconstrained). The suffixes mean: .RS .TP 5 .Op % Treat the dimensions as percentages of the current size. .TP 5 .Op ! Resize to given dimensions ignoring original aspect ratio. .TP 5 .Op < Resize only if the image is smaller than the specified size. .TP 5 .Op > Resize only if the image is larger than the specified size. .TP 5 .Op ^ Dimensions are treated as minimum rather than maximum values. .RE ' .Sp .TP .Oa \-\-scale Xfactor [x Yfactor ] ' Scale the output GIF's width and height by .IR Xfactor " and " Yfactor . If .I Yfactor is not given, it defaults to .IR Xfactor . Scaling happens after all input frames have been combined and before optimization. ' .Sp .TP .Oa \-\-resize\-method method ' Set the method used to resize images. The \(oqsample\(cq method runs very quickly, but when shrinking images, it produces noisy results. The \(oqmix\(cq method is somewhat slower, but produces better-looking results. The default method is currently \(oqmix\(cq. .RS .Sp .PP Details: The resize methods differ most when shrinking images. The \(oqsample\(cq method is a point sampler: each pixel position in the output image maps to exactly one pixel position in the input. When shrinking, full rows and columns from the input are dropped. The other methods use all input pixels, which generally produces better-looking images. The \(oqbox\(cq method, a box sampler, is faster than the more complex filters and produces somewhat sharper results, but there will be anomalies when shrinking images by a small amount in one dimension. (Some output pixels will correspond to exactly 1 input row or column, while others will correspond to exactly 2 input rows or columns.) The \(oqmix\(cq method is a full bilinear interpolator. This is slower and produces somewhat blurrier results, but avoids anomalies. .Sp .PP Gifsicle also supports more complex resamplers, including Catmull-Rom cubic resampling (\(oqcatrom\(cq), the Mitchell-Netravali filter (\(oqmitchell\(cq), a 2-lobed Lanczos filter (\(oqlanczos2\(cq), and a 3-lobed Lanczos filter (\(oqlanczos3\(cq). These filters are slower still, but can give sharper, better results. .RE ' .Sp .TP .Oa \-\-resize\-colors n ' Allow Gifsicle to add intermediate colors when resizing images. Normally, Gifsicle's resize algorithms use input images' color palettes without changes. When shrinking images with very few colors (e.g., pure black-and-white images), adding intermediate colors can improve the results. Example: .Oa \-\-resize\-colors 64 allows Gifsicle to add intermediate colors for images that have fewer than 64 input colors. .PD ' .\" ----------------------------------------------------------------- .SS Color Options ' Color options apply to entire GIFs as they are read or written. They can be turned off with .Qo \-\-no\-option . .Sp .PD 0 .TP 5 .Oa \-k num .TP .Oa \-\-colors num ' Reduce the number of distinct colors in each output GIF to .I num or less. .I Num must be between 2 and 256. This can be used to shrink output GIFs or eliminate any local color tables. .Sp Normally, an adaptive group of colors is chosen from the existing color table. You can affect this process with the .Op \-\-color\-method option or by giving your own colormap with .Op \-\-use\-colormap . Gifsicle may need to add an additional color (making .IR num +1 in all) if there is transparency in the image. ' .Sp .TP .Oa \-\-color\-method method ' Determine how a smaller colormap is chosen. .RB \(oq diversity \(cq, the default, is .BR xv (1)'s diversity algorithm, which uses a strict subset of the existing colors and generally produces good results. .RB \(oq blend\-diversity \(cq is a modification of this: some color values are blended from groups of existing colors. .RB \(oq median\-cut \(cq is the median cut algorithm described by Heckbert. .Op \-\-method is a synonym for .Op \-\-color\-method . ' .Sp .TP 5 .Oa \-f .TP .Op \-\-dither "[=\fImethod\fR]" ' When .Op \-\-dither is on and the colormap is changed, combinations of colors are used to approximate missing colors. This looks better, but makes bigger files and can cause animation artifacts, so it is off by default. .Sp Specify a dithering method with the optional \fImethod\fR argument. The default method, .RB \(oq floyd-steinberg \(cq, uses Floyd-Steinberg error diffusion. This usually looks best, but can cause animation artifacts, because dithering choices will vary from frame to frame. The .RB \(oq atkinson \(cq method uses Atkinson error diffusion, which is a variant of Floyd-Steinberg. It offers a more localized dithering pattern, which can be useful for images with large areas of solid color. Gifsicle also supports ordered dithering methods that avoid animation artifacts. The .RB \(oq ro64 \(cq method uses a large, random-looking pattern and generally produces good results. The .RB \(oq o3 \(cq, .RB \(oq o4 \(cq, and .RB \(oq o8 \(cq methods use smaller, more regular patterns. The .RB \(oq ordered \(cq method chooses a good ordered dithering algorithm. For special effects, try the halftone methods .RB \(oq halftone \(cq, .RB \(oq squarehalftone \(cq, and .RB \(oq diagonal \(cq. ' .Sp Some methods take optional parameters using commas. The halftone methods take a cell size and a color limit: .RB \(oq halftone,10,3 \(cq creates 10-pixel wide halftone cells where each cell uses up to 3 colors. ' .Sp .TP 5 .Oa \-\-gamma gamma ' Set the color space/gamma correction to .IR gamma , which can be a real number, .RB \(oq srgb \(cq (the default), or .RB \(oq oklab \(cq. Roughly speaking, higher numbers exaggerate shadows and lower numbers exaggerate highlights. The default color space is standard sRGB, which usually works well. (Its effects are similar to \fB\-\-gamma\fP=2.2.) Gifsicle uses gamma correction when choosing a color palette (\fB\-\-colors\fP), when dithering (\fB\-\-dither\fP), and when computing color errors during lossy compression (\fB\-\-lossy\fP). ' .Sp .TP .Op \-\-lossy "[=\fIlossiness\fR]" ' Alter image colors to shrink output file size at the cost of artifacts and noise. .I Lossiness determines how many artifacts are allowed; higher values can result in smaller file sizes, but cause more artifacts. The default .I lossiness is 20. ' .Sp Gifsicle uses the color space set by \fB\-\-gamma\fP to evaluate artifacts. If unsatisfied with the operation of \fB\-\-lossy\fP, try another color space, such as \fB\-\-gamma\fP=1. ' .Sp .PD 0 .TP .Oa \-\-change\-color color1 " " color2 ' Change .I color1 to .I color2 in the following input GIFs. (The .I color arguments have the same forms as in the .Op \-t option.) Change multiple colors by giving the option multiple times. Color changes don't interfere with one another, so you can safely swap two colors with .Qa \-\-change\-color "color1 color2" \-\-change\-color "color2 color1" . They all take effect as an input GIF is read. .Op \-\-no\-change\-color cancels all color changes. ' .Sp .TP .Oa \-\-transform\-colormap command ' .I Command should be a shell command that reads from standard input and writes to standard output. Each colormap in the output GIF is translated into text colormap format (see .Op \-\-use\-colormap below) and piped to the command. The output that command generates (which should also be in text colormap format) will replace the input colormap. The replacement doesn't consider color matching, so pixels that used color slot .I n in the input will still use color slot .I n in the output. ' .Sp .TP .Oa \-\-use\-colormap colormap ' Change the image to use .IR colormap . Each pixel in the image is changed to the closest match in .I colormap (or, if .Op \-\-dither is on, to a dithered combination of colors in .IR colormap ")." .I Colormap can be .BR web for the 216-color \(lqWeb-safe palette\(rq; .BR gray for grayscale; .BR bw for black-and-white; or the name of a file. That file should either be a text file (the format is described below) or a GIF file, whose global colormap will be used. If .Op \-\-colors\fR=\fIN is also given, an .IR N \-sized subset of .I colormap will be used. .Sp Text colormap files use this format: .Es ; each non-comment line represents one color, "red green blue" ; each component should be between 0 and 255 0 0 0 ; like this 255 255 255 ; or use web hex notation #ffffff ; like this .Ee ' .Sp .TP .Oa \-\-use\-exact\-colormap colormap ' Same as .Op \-\-use\-colormap\fR, but the output GIF will use .IR colormap as is, rather than reordering it and dropping unused colors. ' .PD .PP .br ' .SH EXAMPLES ' First, let's create an animation, \(oqanim.gif\(cq: .Es \fBgifsicle a.gif b.gif c.gif d.gif > anim.gif\fR .Ee This animation will move very quickly: since we didn't specify a delay, a browser will cycle through the frames as fast as it can. Let's slow it down and pause .5 seconds between frames, using the .Op \-\-delay option. .Es \fBgifsicle \-\-delay 50 a.gif b.gif c.gif d.gif > anim.gif\fR .Ee If we also want the GIF to loop three times, we can use .Op \-\-loopcount : .Es \fBgifsicle \-d 50 \-\-loop=3 a.gif b.gif c.gif d.gif > anim.gif\fR .Ee (Rather than type .Op \-\-delay again, we used its short form, .Op \-d . Many options have short forms; you can see them by running .RB \(oq "gifsicle \-\-help" \(cq. We also abbreviated .Op \-\-loopcount to .Op \-\-loop , which is OK since no other option starts with \(oqloop\(cq.) .PP To explode \(oqanim.gif\(cq into its component frames: .Es \fBgifsicle \-\-explode anim.gif\fR .br \fBls anim.gif*\fR .br anim.gif anim.gif.000 anim.gif.001 anim.gif.002 anim.gif.003 .Ee To optimize \(oqanim.gif\(cq: .Es \fBgifsicle \-b \-O2 anim.gif\fR .Ee To change the second frame of \(oqanim.gif\(cq to \(oqx.gif\(cq: .Es \fBgifsicle \-b \-\-unoptimize \-O2 anim.gif \-\-replace "#1" x.gif\fR .Ee .Op \-\-unoptimize is used since \(oqanim.gif\(cq was optimized in the last step. Editing individual frames in optimized GIFs is dangerous without .Op \-\-unoptimize ; frames following the changed frame could be corrupted by the change. Of course, this might be what you want. .PP Note that .Op \-\-unoptimize and .Op \-\-optimize can be on simultaneously. .Op \-\-unoptimize affects .I input GIF files, while .Op \-\-optimize affects .I output GIF files. .PP To print information about the first and fourth frames of \(oqanim.gif\(cq: .Es \fBgifsicle \-I "#0" "#3" < anim.gif\fR .Ee To make black the transparent color in all the GIFs in the current directory, and also print information about each: .Es \fBgifsicle \-bII \-\-trans "#000000" *.gif\fR .Ee Giving .Op \-I twice forces normal output to occur. With only one .Op \-I , the GIFs would not be modified. .PP To change \(oqanim.gif\(cq to use a 64-color subset of the Web-safe palette: .Es \fBgifsicle \-b \-\-colors=64 \-\-use\-col=web anim.gif\fR .Ee To make a dithered black-and-white version of \(oqanim.gif\(cq: .Es \fBgifsicle \-\-dither \-\-use\-col=bw anim.gif > anim-bw.gif\fR .Ee .PP To overlay one GIF atop another\*Eproducing a one-frame output GIF that looks like the superposition of the two inputs\*Euse .B gifsicle twice: .Es \fBgifsicle bottom.gif top.gif | gifsicle \-U "#1" > result.gif\fR .Ee ' .SH BUGS ' Some optimized output GIFs may appear incorrectly on some GIF implementations (for example, Java's); see the .Op \-\-careful option. .PP Please email suggestions, additions, patches and bugs to ekohler@gmail.com. ' .SH "SEE ALSO" ' For a tutorial on GIF images and animations, you might try some of the resources listed on-line at webreference\%.com: .br http://www.webreference.com/authoring/graphics/animation\|.html ' .SH AUTHORS .na Eddie Kohler .br http://www.read.seas.harvard.edu/~kohler/ .br He wrote it. .PP Anne Dudfield .br http://www.frii.com/~annied/ .br She named it. .PP Hans Dinsen-Hansen .br http://www.danbbs.dk/~dino/ .br Adaptive tree method for GIF writing. .PP Kornel Lesin\*[']ski .br .Op \-\-lossy option. .PP http://www.lcdf.org/gifsicle/ .br The .B gifsicle home page. ' gifsicle-1.96/gifview.1000066400000000000000000000136061475770763400150120ustar00rootroot00000000000000.\" -*- mode: nroff -*- .ds V 1.96 .ds E " \-\- .if t .ds E \(em .de Op .BR "\\$1" "\\$2" "\\$3" "\\$4" "\\$5" "\\$6" .. .de Oa .IR "\fB\\$1\& \|\fI\\$2" "\\$3" "\\$4" "\\$5" "\\$6" .. .de Qo .RB ` "\\$1" "'\\$2" .. .de Sp .if n .sp .if t .sp 0.4 .. .de Es .Sp .RS 5 .nf .. .de Ee .fi .RE .PP .. .TH GIFVIEW 1 "11 July 2017" "Version \*V" .SH NAME gifview \- displays GIF images and animations on the X window system .SH SYNOPSIS .B gifview \%[\fB--display\fP \fIdisplay\fP] \%[options] \%[filenames and frames].\|.\|. ' .SH DESCRIPTION .B gifview displays GIF image files on workstations and terminals running the X Window System. .B gifview understands multi-image GIFs, which can be displayed either as slideshows or as animations. ' .SH INTERACTION .B gifview windows recognize several keystrokes and button commands. Many of them are only useful for multi-image GIFs. .TP 12 .RB "Space or " n ' Go to the next frame. .TP .BR b " or " p Go to the previous frame. .TP .BR r " or " < Go to the first frame. .TP .BR > Go to the last frame. .TP ESC Stop the animation. .TP .BR s " or " a Toggle between animation and slideshow mode. .TP .BR u Toggle between normal and unoptimized mode. .TP Backspace ' Delete this window. .TP .B q Quit .BR gifview . ' .PP Left-clicking on a window goes to the next frame; right-clicking on a window deletes that window. ' .SH COMMAND LINE .BR gifview 's command line consists of .IR "GIF input files" and .IR options . Most options start with a dash (\-) or plus (+); frame selections, a kind of option, start with a number sign (#). Anything else is a GIF input file. .PP .B gifview displays one window for each GIF input file you specify. If no GIF input file is given, or you give the special filename `\-', it reads from the standard input. ' .SH OPTIONS .PD 0 .TP 5 .Op \-\-animate ", " \-a ' Animate multi-image GIFs by default. Normally, multi-image GIFs first appear in slideshow mode. You can always use the .RB ` a ' keystroke to toggle between modes. This option has a converse, .Qo \-\-no\-animate or .Qo +a . ' .Sp .TP 5 .Op \-\-unoptimize ", " \-U ' Display multi-image GIFs as ``unoptimized'', which shows a faithful representation of what a user will see at each frame of an animation. See .BR gifsicle (1) for a more detailed description of unoptimization. This option has a converse, .Qo \-\-no\-unoptimize or .Qo +U . GIFs are always displayed unoptimized in animation mode. ' .Sp .TP 5 .Oa \-d display .TP .Oa \-\-display display ' Sets the X display to .IR display . This option must come before any GIF files. ' .Sp .TP 5 .Oa \-\-name name ' Sets the application name under which resources are found, rather than the default of "gifview". Since .B gifview itself does not use the resource database, this is mostly useful for communication with your window manager. ' .Sp .TP 5 .Oa \-\-geometry geometry ' Set the size and position of .BR gifview 's windows. This is a standard X option. At most one .Op \-\-geometry option can be given per window (that is, per input GIF file). ' .Sp .TP 5 .Oa \-\-title title ' Sets the .B gifview window's title. The default is "gifview", followed by information about the currently displayed file and frame. ' .Sp .TP 5 .Oa \-w window .TP .Oa \-\-window window ' Display the next GIF input in an existing X window, instead of making a new top-level window. This way, you can use .B gifview to display animated GIFs in a window you created with another program. The .I window argument should be an integer .RB ( gifview will use that window ID) or `root' .RB ( gifview will use the root window). ' .Sp .TP 5 .Oa \-\-new\-window window ' Display the next GIF input in a new child of an existing X window. This child window will disappear when .B gifview exits. The .I window argument should be an integer .RB ( gifview will use that window ID) or `root' .RB ( gifview will use the root window). ' .Sp .TP 5 .Op \-\-install\-colormap ", " \-i ' Use a private colormap for each window (if you are using a PseudoColor display). This avoids polluting the existing colormap, and may produce better results if your colormap is full, but causes annoying colormap flashing. ' .Sp .TP 5 .Oa \-\-background color .TP .Oa \-\-bg color ' Set the background color, which is used for transparent pixels. ' .Sp .TP 5 .Oa \-\-min\-delay delay ' Set the minimum delay between frames to .IR delay , which is measured in hundredths of a second. Default is 0. ' .Sp .TP 5 .Oa \-\-fallback\-delay delay ' Set the frame delay of GIFs that do not specify a delay value or have a delay of 0. The final value is still subject to the value of \-\-min\-delay. Like \-\-min\-delay, .IR delay is measured in hundredths of a second. Default is 0. ' .Sp .TP 5 .Op \-\-no\-interactive ", " \+e ' Don't pay attention to mouse buttons or keystrokes. ' .Sp .TP 5 .Op \-\-memory\-limit lim ' Cache at most .I lim megabytes of images in memory when animating. Default is 40. ' .Sp .TP 5 .Op \-\-help ' Print usage information and exit. ' .Sp .TP .Op \-\-version ' Print the version number and some quickie warranty information and exit. ' .PD ' .\" ----------------------------------------------------------------- .SS Frame Selections A frame selection tells .B gifview which frame to initially display from the current input file. They are useful only for animations, as non-animated GIFs only have one frame. Frame selections can only be displayed in slideshow mode. .Sp .PD 0 .TP 13 .BI # num ' Select frame \fInum\fR. (The first frame is .Qo #0 . Negative numbers count backwards from the last frame, which is .Qo #-1 .) ' .TP 13 .BI # name ' Select the frame named \fIname\fR. .PD .PP If you give two or more frame selections, you will get one window per frame selection. ' .SH SEE ALSO .BR gifsicle (1) ' .SH BUGS Please email suggestions, additions, patches and bugs to ekohler@gmail.com. ' .SH AUTHORS .na Eddie Kohler, ekohler@gmail.com .br http://www.read.seas.harvard.edu/~kohler/ .PP http://www.lcdf.org/gifsicle/ .br The .B gifsicle home page. ' gifsicle-1.96/include/000077500000000000000000000000001475770763400147055ustar00rootroot00000000000000gifsicle-1.96/include/lcdf/000077500000000000000000000000001475770763400156155ustar00rootroot00000000000000gifsicle-1.96/include/lcdf/clp.h000066400000000000000000000331331475770763400165470ustar00rootroot00000000000000/* -*- related-file-name: "../../liblcdf/clp.c" -*- */ #ifndef LCDF_CLP_H #define LCDF_CLP_H #ifdef __cplusplus extern "C" { #endif /* clp.h - Public interface to CLP. * This file is part of CLP, the command line parser package. * * Copyright (c) 1997-2025 Eddie Kohler, ekohler@gmail.com * * CLP is free software. It is distributed under the GNU General Public * License, Version 2, or, alternatively and at your discretion, under the * more permissive (BSD-like) Click LICENSE file as described below. * * 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, subject to the * conditions listed in the Click LICENSE file, which is available in full at * http://github.com/kohler/click/blob/master/LICENSE. The conditions * include: you must preserve this copyright notice, and you cannot mention * the copyright holders in advertising related to the Software without * their permission. The Software is provided WITHOUT ANY WARRANTY, EXPRESS * OR IMPLIED. This notice is a summary of the Click LICENSE file; the * license in that file is binding. */ #include #include typedef struct Clp_Option Clp_Option; typedef struct Clp_Parser Clp_Parser; typedef struct Clp_ParserState Clp_ParserState; /** @brief Option description. * * CLP users declare arrays of Clp_Option structures to specify what options * should be parsed. * @sa Clp_NewParser, Clp_SetOptions */ struct Clp_Option { const char *long_name; /**< Name of long option, or NULL if the option has no long name. */ int short_name; /**< Character defining short option, or 0 if the option has no short name. */ int option_id; /**< User-specified ID defining option, returned by Clp_Next. */ int val_type; /**< ID of option's value type, or 0 if option takes no value. */ int flags; /**< Option parsing flags. */ }; /** @name Value types * These values describe the type of an option's argument and are used in * the Clp_Option val_type field. For example, if an option took integers, its * Clp_Option structure would have val_type set to Clp_ValInt. */ /**@{*/ #define Clp_NoVal 0 /**< @brief Option takes no value. */ #define Clp_ValString 1 /**< @brief Option value is an arbitrary string. */ #define Clp_ValStringNotOption 2 /**< @brief Option value is a non-option string. See Clp_DisallowOptions. */ #define Clp_ValBool 3 /**< @brief Option value is a boolean. Accepts "true", "false", "yes", "no", "1", and "0", or any prefixes thereof. The match is case-insensitive. */ #define Clp_ValInt 4 /**< @brief Option value is a signed int. Accepts an optional "+" or "-" sign, followed by one or more digits. The digits may be include a "0x" or "0X" prefix, for a hexadecimal number, or a "0" prefix, for an octal number; otherwise it is decimal. */ #define Clp_ValUnsigned 5 /**< @brief Option value is an unsigned int. Accepts an optional "+" sign, followed by one or more digits. The digits may be include a "0x" or "0X" prefix, for a hexadecimal number, or a "0" prefix, for an octal number; otherwise it is decimal. */ #define Clp_ValLong 6 /**< @brief Option value is a signed long. */ #define Clp_ValUnsignedLong 7 /**< @brief Option value is an unsigned long. */ #define Clp_ValDouble 8 /**< @brief Option value is a double. Accepts a real number as defined by strtod(). */ #define Clp_ValFirstUser 10 /**< @brief Value types >= Clp_ValFirstUser are available for user types. */ /**@}*/ /** @name Option flags * These flags are used in the Clp_Option flags field. */ /**@{*/ #define Clp_Mandatory (1<<0) /**< @brief Option flag: value is mandatory. It is an error if the option has no value. This is the default if an option has arg_type != 0 and the Clp_Optional flag is not provided. */ #define Clp_Optional (1<<1) /**< @brief Option flag: value is optional. */ #define Clp_Negate (1<<2) /**< @brief Option flag: option may be negated. --no-[long_name] will be accepted in argument lists. */ #define Clp_OnlyNegated (1<<3) /**< @brief Option flag: option must be negated. --no-[long_name] will be accepted in argument lists, but --[long_name] will not. This is the default if long_name begins with "no-". */ #define Clp_PreferredMatch (1<<4) /**< @brief Option flag: prefer this option when matching. Prefixes of --[long_name] should map to this option, even if other options begin with --[long_name]. */ /**@}*/ /** @name Option character types * These flags are used in to define character types in Clp_SetOptionChar(). */ /**@{*/ /* Clp_NotOption 0 */ #define Clp_Short (1<<0) /**< @brief Option character begins a set of short options. */ #define Clp_Long (1<<1) /**< @brief Option character begins a long option. */ #define Clp_ShortNegated (1<<2) /**< @brief Option character begins a set of negated short options. */ #define Clp_LongNegated (1<<3) /**< @brief Option character begins a negated long option. */ #define Clp_LongImplicit (1<<4) /**< @brief Option character can begin a long option, and is part of that long option. */ /**@}*/ #define Clp_NotOption 0 /**< @brief Clp_Next value: argument was not an option. */ #define Clp_Done -1 /**< @brief Clp_Next value: there are no more arguments. */ #define Clp_BadOption -2 /**< @brief Clp_Next value: argument was an erroneous option. */ #define Clp_Error -3 /**< @brief Clp_Next value: internal CLP error. */ #define Clp_ValSize 40 /**< @brief Minimum size of the Clp_Parser val.cs field. */ #define Clp_ValIntSize 10 /**< @brief Minimum size of the Clp_Parser val.is field. */ /** @brief A value parsing function. * @param clp the parser * @param vstr the value to be parsed * @param complain if nonzero, report error messages via Clp_OptionError * @param user_data user data passed to Clp_AddType() * @return 1 if parsing succeeded, 0 otherwise */ typedef int (*Clp_ValParseFunc)(Clp_Parser *clp, const char *vstr, int complain, void *user_data); /** @brief A function for reporting option errors. * @param clp the parser * @param message error message */ typedef void (*Clp_ErrorHandler)(Clp_Parser *clp, const char *message); /** @brief Command line parser. * * A Clp_Parser object defines an instance of CLP, including allowed options, * value types, and current arguments. * @sa Clp_NewParser, Clp_SetOptions, Clp_SetArguments */ struct Clp_Parser { const Clp_Option *option; /**< The last option. */ int negated; /**< Whether the last option was negated. */ int have_val; /**< Whether the last option had a value. */ const char *vstr; /**< The string value provided with the last option. */ union { int i; unsigned u; long l; unsigned long ul; double d; const char *s; void *pv; #ifdef HAVE_INT64_TYPES int64_t i64; uint64_t u64; #endif char cs[Clp_ValSize]; unsigned char ucs[Clp_ValSize]; int is[Clp_ValIntSize]; unsigned us[Clp_ValIntSize]; } val; /**< The parsed value provided with the last option. */ void *user_data; /**< Uninterpreted by CLP; users can set arbitrarily. */ struct Clp_Internal *internal; }; /** @cond never */ #if __GNUC__ >= 4 # define CLP_SENTINEL __attribute__((sentinel)) #else # define CLP_SENTINEL /* nothing */ #endif /** @endcond never */ /** @brief Create a new Clp_Parser. */ Clp_Parser *Clp_NewParser(int argc, const char * const *argv, int nopt, const Clp_Option *opt); /** @brief Destroy a Clp_Parser object. */ void Clp_DeleteParser(Clp_Parser *clp); /** @brief Return @a clp's program name. */ const char *Clp_ProgramName(Clp_Parser *clp); /** @brief Set @a clp's program name. */ const char *Clp_SetProgramName(Clp_Parser *clp, const char *name); /** @brief Set @a clp's error handler function. */ Clp_ErrorHandler Clp_SetErrorHandler(Clp_Parser *clp, Clp_ErrorHandler errh); /** @brief Set @a clp's UTF-8 mode. */ int Clp_SetUTF8(Clp_Parser *clp, int utf8); /** @brief Return @a clp's treatment of character @a c. */ int Clp_OptionChar(Clp_Parser *clp, int c); /** @brief Set @a clp's treatment of character @a c. */ int Clp_SetOptionChar(Clp_Parser *clp, int c, int type); /** @brief Set @a clp's option definitions. */ int Clp_SetOptions(Clp_Parser *clp, int nopt, const Clp_Option *opt); /** @brief Set @a clp's arguments. */ void Clp_SetArguments(Clp_Parser *clp, int argc, const char * const *argv); /** @brief Set whether @a clp is searching for options. */ int Clp_SetOptionProcessing(Clp_Parser *clp, int on); #define Clp_DisallowOptions (1<<0) /**< @brief Value type flag: value can't be an option string. See Clp_AddType(). */ /** @brief Define a new value type for @a clp. */ int Clp_AddType(Clp_Parser *clp, int val_type, int flags, Clp_ValParseFunc parser, void *user_data); #define Clp_AllowNumbers (1<<0) /**< @brief String list flag: allow explicit numbers. See Clp_AddStringListType() and Clp_AddStringListTypeVec(). */ #define Clp_StringListLong (1<<1) /**< @brief String list flag: values have long type. */ /** @brief Define a new string list value type for @a clp. */ int Clp_AddStringListTypeVec(Clp_Parser *clp, int val_type, int flags, int nstrs, const char * const *strs, const int *vals); /** @brief Define a new string list value type for @a clp. */ int Clp_AddStringListType(Clp_Parser *clp, int val_type, int flags, ...) CLP_SENTINEL; /** @brief Parse and return the next argument from @a clp. */ int Clp_Next(Clp_Parser *clp); /** @brief Return the next argument from @a clp without option parsing. */ const char *Clp_Shift(Clp_Parser *clp, int allow_options); /** @brief Create a new Clp_ParserState. */ Clp_ParserState *Clp_NewParserState(void); /** @brief Destroy a Clp_ParserState object. */ void Clp_DeleteParserState(Clp_ParserState *state); /** @brief Save @a clp's current state in @a state. */ void Clp_SaveParser(const Clp_Parser *clp, Clp_ParserState *state); /** @brief Restore parser state from @a state into @a clp. */ void Clp_RestoreParser(Clp_Parser *clp, const Clp_ParserState *state); /** @brief Report a parser error. */ int Clp_OptionError(Clp_Parser *clp, const char *format, ...); /** @brief Format a message. */ int Clp_vsnprintf(Clp_Parser* clp, char* str, size_t size, const char* format, va_list val); /** @brief Print a message. */ int Clp_fprintf(Clp_Parser* clp, FILE* f, const char* format, ...); /** @brief Print a message. */ int Clp_vfprintf(Clp_Parser* clp, FILE* f, const char* format, va_list val); /** @brief Extract the current option as a string. */ int Clp_CurOptionNameBuf(Clp_Parser *clp, char *buf, int len); /** @brief Extract the current option as a string. */ const char *Clp_CurOptionName(Clp_Parser *clp); /** @brief Test if the current option had long name @a name. */ int Clp_IsLong(Clp_Parser *clp, const char *long_name); /** @brief Test if the current option had short name @a name. */ int Clp_IsShort(Clp_Parser *clp, int short_name); /** @brief Return a UTF-8 or normal single quote. */ const char* Clp_QuoteChar(const Clp_Parser* clp, int isclose); #undef CLP_SENTINEL #ifdef __cplusplus } #endif #endif gifsicle-1.96/include/lcdf/inttypes.h000066400000000000000000000013061475770763400176450ustar00rootroot00000000000000#ifndef LCDF_INTTYPES_H #define LCDF_INTTYPES_H /* Define known-width integer types. */ #ifdef HAVE_INTTYPES_H # include #elif defined(HAVE_SYS_TYPES_H) # include # ifdef HAVE_U_INT_TYPES typedef u_int8_t uint8_t; typedef u_int16_t uint16_t; typedef u_int32_t uint32_t; # endif #elif defined(_WIN32) typedef __int8 int8_t; typedef unsigned __int8 uint8_t; typedef __int16 int16_t; typedef unsigned __int16 uint16_t; typedef __int32 int32_t; typedef unsigned __int32 uint32_t; #endif #ifndef HAVE_UINTPTR_T # if SIZEOF_VOID_P == SIZEOF_UNSIGNED_LONG typedef unsigned long uintptr_t; # elif SIZEOF_VOID_P == SIZEOF_UNSIGNED_INT typedef unsigned int uintptr_t; # endif #endif #endif gifsicle-1.96/include/lcdfgif/000077500000000000000000000000001475770763400163035ustar00rootroot00000000000000gifsicle-1.96/include/lcdfgif/gif.h000066400000000000000000000246641475770763400172350ustar00rootroot00000000000000/* gif.h - Interface to the LCDF GIF library. Copyright (C) 1997-2025 Eddie Kohler, ekohler@gmail.com This file is part of the LCDF GIF library. The GIF library is free software. It is distributed under the GNU General Public License, version 2; you can copy, distribute, or alter it at will, as long as this notice is kept intact and this source code is made available. There is no warranty, express or implied. */ #ifndef LCDF_GIF_H /* -*- mode: c -*- */ #define LCDF_GIF_H #include #include #include #ifdef __cplusplus extern "C" { #endif /* NOTE: You should define the types uint8_t, uint16_t and uint32_t before including this file, probably by #including . */ #define GIF_MAJOR_VERSION 2 #define GIF_MINOR_VERSION 0 #define GIF_VERSION "2.0" typedef struct Gif_Stream Gif_Stream; typedef struct Gif_Image Gif_Image; typedef struct Gif_Colormap Gif_Colormap; typedef struct Gif_Comment Gif_Comment; typedef struct Gif_Extension Gif_Extension; typedef struct Gif_Record Gif_Record; typedef uint16_t Gif_Code; #define GIF_MAX_CODE_BITS 12 #define GIF_MAX_CODE 0x1000 #define GIF_MAX_BLOCK 255 /** GIF_STREAM **/ struct Gif_Stream { Gif_Image **images; int nimages; int imagescap; Gif_Colormap *global; uint16_t background; /* 256 means no background */ uint16_t screen_width; uint16_t screen_height; long loopcount; /* -1 means no loop count */ Gif_Comment* end_comment; Gif_Extension* end_extension_list; unsigned errors; uint32_t user_flags; const char* landmark; int refcount; }; Gif_Stream * Gif_NewStream(void); void Gif_DeleteStream(Gif_Stream *); Gif_Stream * Gif_CopyStreamSkeleton(Gif_Stream *); Gif_Stream * Gif_CopyStreamImages(Gif_Stream *); #define Gif_ScreenWidth(gfs) ((gfs)->screen_width) #define Gif_ScreenHeight(gfs) ((gfs)->screen_height) #define Gif_ImageCount(gfs) ((gfs)->nimages) #define GIF_UNOPTIMIZE_SIMPLEST_DISPOSAL 1 #define GIF_MAX_SCREEN_WIDTH 65535 #define GIF_MAX_SCREEN_HEIGHT 65535 void Gif_CalculateScreenSize(Gif_Stream *, int force); int Gif_Unoptimize(Gif_Stream *); int Gif_FullUnoptimize(Gif_Stream *, int flags); /** GIF_IMAGE **/ struct Gif_Image { uint8_t **img; /* img[y][x] == image byte (x,y) */ uint8_t *image_data; uint16_t width; uint16_t height; uint16_t left; uint16_t top; uint16_t delay; uint8_t disposal; uint8_t interlace; short transparent; /* -1 means no transparent index */ Gif_Colormap *local; char* identifier; Gif_Comment* comment; Gif_Extension* extension_list; void (*free_image_data)(void *); uint32_t compressed_len; uint32_t compressed_errors; uint8_t* compressed; void (*free_compressed)(void *); uint32_t user_flags; void* user_data; void (*free_user_data)(void *); int refcount; }; #define GIF_DISPOSAL_NONE 0 #define GIF_DISPOSAL_ASIS 1 #define GIF_DISPOSAL_BACKGROUND 2 #define GIF_DISPOSAL_PREVIOUS 3 Gif_Image * Gif_NewImage(void); void Gif_DeleteImage(Gif_Image *gfi); int Gif_AddImage(Gif_Stream *gfs, Gif_Image *gfi); void Gif_RemoveImage(Gif_Stream *gfs, int i); Gif_Image * Gif_CopyImage(Gif_Image *gfi); void Gif_MakeImageEmpty(Gif_Image* gfi); Gif_Image * Gif_GetImage(Gif_Stream *gfs, int i); Gif_Image * Gif_GetNamedImage(Gif_Stream *gfs, const char *name); int Gif_ImageNumber(Gif_Stream *gfs, Gif_Image *gfi); #define Gif_ImageWidth(gfi) ((gfi)->width) #define Gif_ImageHeight(gfi) ((gfi)->height) #define Gif_ImageDelay(gfi) ((gfi)->delay) #define Gif_ImageUserData(gfi) ((gfi)->userdata) #define Gif_SetImageUserData(gfi, v) ((gfi)->userdata = v) int Gif_ImageColorBound(const Gif_Image* gfi); typedef void (*Gif_ReadErrorHandler)(Gif_Stream* gfs, Gif_Image* gfi, int is_error, const char* error_text); typedef struct { int flags; int loss; void *padding[7]; } Gif_CompressInfo; #define Gif_UncompressImage(gfs, gfi) Gif_FullUncompressImage((gfs),(gfi),0) int Gif_FullUncompressImage(Gif_Stream* gfs, Gif_Image* gfi, Gif_ReadErrorHandler handler); int Gif_CompressImage(Gif_Stream *gfs, Gif_Image *gfi); int Gif_FullCompressImage(Gif_Stream *gfs, Gif_Image *gfi, const Gif_CompressInfo *gcinfo); void Gif_ReleaseUncompressedImage(Gif_Image *gfi); void Gif_ReleaseCompressedImage(Gif_Image *gfi); int Gif_SetUncompressedImage(Gif_Image *gfi, uint8_t *data, void (*free_data)(void *), int data_interlaced); int Gif_CreateUncompressedImage(Gif_Image* gfi, int data_interlaced); int Gif_ClipImage(Gif_Image *gfi, int l, int t, int w, int h); void Gif_InitCompressInfo(Gif_CompressInfo *gcinfo); /** GIF_COLORMAP **/ typedef struct { uint8_t haspixel; /* semantics assigned by user */ uint8_t gfc_red; /* red component (0-255) */ uint8_t gfc_green; /* green component (0-255) */ uint8_t gfc_blue; /* blue component (0-255) */ uint32_t pixel; /* semantics assigned by user */ } Gif_Color; struct Gif_Colormap { int ncol; int capacity; uint32_t user_flags; int refcount; Gif_Color *col; }; Gif_Colormap * Gif_NewColormap(void); Gif_Colormap * Gif_NewFullColormap(int count, int capacity); void Gif_DeleteColormap(Gif_Colormap *); Gif_Colormap * Gif_CopyColormap(Gif_Colormap *); int Gif_ColorEq(Gif_Color *, Gif_Color *); #define GIF_COLOREQ(c1, c2) \ ((c1)->gfc_red==(c2)->gfc_red && (c1)->gfc_green==(c2)->gfc_green && \ (c1)->gfc_blue==(c2)->gfc_blue) #define GIF_SETCOLOR(c, r, g, b) \ ((c)->gfc_red = (r), (c)->gfc_green = (g), (c)->gfc_blue = (b)) int Gif_FindColor(Gif_Colormap *, Gif_Color *); int Gif_AddColor(Gif_Colormap *, Gif_Color *, int look_from); /** GIF_COMMENT **/ struct Gif_Comment { char **str; int *len; int count; int cap; }; Gif_Comment * Gif_NewComment(void); void Gif_DeleteComment(Gif_Comment *); int Gif_AddCommentTake(Gif_Comment *, char *, int); int Gif_AddComment(Gif_Comment *, const char *, int); /** GIF_EXTENSION **/ struct Gif_Extension { int kind; /* negative kinds are reserved */ char* appname; int applength; uint8_t* data; uint32_t length; int packetized; Gif_Stream *stream; Gif_Image *image; Gif_Extension *next; void (*free_data)(void *); }; Gif_Extension* Gif_NewExtension(int kind, const char* appname, int applength); void Gif_DeleteExtension(Gif_Extension* gfex); Gif_Extension* Gif_CopyExtension(Gif_Extension* gfex); int Gif_AddExtension(Gif_Stream* gfs, Gif_Image* gfi, Gif_Extension* gfex); /** READING AND WRITING **/ struct Gif_Record { const unsigned char *data; uint32_t length; }; #define GIF_READ_COMPRESSED 1 #define GIF_READ_UNCOMPRESSED 2 #define GIF_READ_CONST_RECORD 4 #define GIF_READ_TRAILING_GARBAGE_OK 8 #define GIF_WRITE_CAREFUL_MIN_CODE_SIZE 1 #define GIF_WRITE_EAGER_CLEAR 2 #define GIF_WRITE_OPTIMIZE 4 #define GIF_WRITE_SHRINK 8 void Gif_SetErrorHandler(Gif_ReadErrorHandler handler); Gif_Stream* Gif_ReadFile(FILE* f); Gif_Stream* Gif_FullReadFile(FILE* f, int flags, const char* landmark, Gif_ReadErrorHandler handler); Gif_Stream* Gif_ReadRecord(const Gif_Record* record); Gif_Stream* Gif_FullReadRecord(const Gif_Record* record, int flags, const char* landmark, Gif_ReadErrorHandler handler); int Gif_WriteFile(Gif_Stream *gfs, FILE *f); int Gif_FullWriteFile(Gif_Stream *gfs, const Gif_CompressInfo *gcinfo, FILE *f); #define Gif_ReadFile(f) Gif_FullReadFile((f),GIF_READ_UNCOMPRESSED,0,0) #define Gif_ReadRecord(r) Gif_FullReadRecord((r),GIF_READ_UNCOMPRESSED,0,0) #define Gif_CompressImage(s, i) Gif_FullCompressImage((s),(i),0) #define Gif_WriteFile(s, f) Gif_FullWriteFile((s),0,(f)) typedef struct Gif_Writer Gif_Writer; Gif_Writer* Gif_IncrementalWriteFileInit(Gif_Stream* gfs, const Gif_CompressInfo* gcinfo, FILE *f); int Gif_IncrementalWriteImage(Gif_Writer* grr, Gif_Stream* gfs, Gif_Image* gfi); int Gif_IncrementalWriteComplete(Gif_Writer* grr, Gif_Stream* gfs); /** HOOKS AND MISCELLANEOUS **/ int Gif_InterlaceLine(int y, int height); char * Gif_CopyString(const char *); #define GIF_T_STREAM (0) #define GIF_T_IMAGE (1) #define GIF_T_COLORMAP (2) typedef void (*Gif_DeletionHookFunc)(int, void *, void *); int Gif_AddDeletionHook(int, Gif_DeletionHookFunc, void *); void Gif_RemoveDeletionHook(int, Gif_DeletionHookFunc, void *); #ifdef GIF_DEBUGGING #define GIF_DEBUG(x) Gif_Debug x void Gif_Debug(char *x, ...); #else #define GIF_DEBUG(x) #endif void* Gif_Realloc(void* p, size_t s, size_t n, const char* file, int line); void Gif_Free(void* p); #if !GIF_ALLOCATOR_DEFINED # define Gif_Free free #endif #ifndef Gif_New # define Gif_New(t) ((t*) Gif_Realloc(0, sizeof(t), 1, __FILE__, __LINE__)) # define Gif_NewArray(t, n) ((t*) Gif_Realloc(0, sizeof(t), (n), __FILE__, __LINE__)) # define Gif_ReArray(p, t, n) ((p)=(t*) Gif_Realloc((void*) (p), sizeof(t), (n), __FILE__, __LINE__)) # define Gif_Delete(p) Gif_Free((void*) (p)) # define Gif_DeleteArray(p) Gif_Free((void*) (p)) #endif #ifdef __cplusplus } #endif #endif gifsicle-1.96/include/lcdfgif/gifx.h000066400000000000000000000055701475770763400174200ustar00rootroot00000000000000#ifndef LCDF_GIFX_H #define LCDF_GIFX_H #include #ifdef __cplusplus extern "C" { #endif /* gifx.h - Functions to turn GIFs in memory into X Pixmaps. Copyright (C) 1997-2025 Eddie Kohler, ekohler@gmail.com This file is part of the LCDF GIF library. The LCDF GIF library is free software*. It is distributed under the GNU General Public License, version 2; you can copy, distribute, or alter it at will, as long as this notice is kept intact and this source code is made available. There is no warranty, express or implied. */ #include #define GIFX_COLORMAP_EXTENSION -107 typedef struct Gif_XContext Gif_XContext; typedef struct Gif_XColormap Gif_XColormap; typedef struct Gif_XFrame Gif_XFrame; struct Gif_XContext { Display *display; int screen_number; Drawable drawable; Visual *visual; uint16_t depth; uint16_t ncolormap; Colormap colormap; uint16_t nclosest; Gif_Color *closest; int free_deleted_colormap_pixels; Gif_XColormap *xcolormap; GC image_gc; GC mask_gc; unsigned long transparent_pixel; unsigned long foreground_pixel; int refcount; }; struct Gif_XFrame { Pixmap pixmap; int postdisposal; int user_data; }; Gif_XContext * Gif_NewXContext(Display *display, Window window); Gif_XContext * Gif_NewXContextFromVisual(Display *display, int screen_number, Visual *visual, int depth, Colormap cmap); void Gif_DeleteXContext(Gif_XContext *gfx); Pixmap Gif_XImage(Gif_XContext *gfx, Gif_Stream *gfs, Gif_Image *gfi); Pixmap Gif_XImageColormap(Gif_XContext *gfx, Gif_Stream *gfs, Gif_Colormap *gfcm, Gif_Image *gfi); Pixmap Gif_XSubImage(Gif_XContext *gfx, Gif_Stream *gfs, Gif_Image *gfi, int l, int t, int w, int h); Pixmap Gif_XSubImageColormap(Gif_XContext *gfx, Gif_Stream* gfs, Gif_Image *gfi, Gif_Colormap* gfcm, int l, int t, int w, int h); Pixmap Gif_XMask(Gif_XContext *gfx, Gif_Stream *gfs, Gif_Image *gfi); Pixmap Gif_XSubMask(Gif_XContext* gfx, Gif_Stream* gfs, Gif_Image* gfi, int l, int t, int w, int h); Gif_XFrame * Gif_NewXFrames(Gif_Stream *gfs); void Gif_DeleteXFrames(Gif_XContext *gfx, Gif_Stream *gfs, Gif_XFrame *frames); Pixmap Gif_XNextImage(Gif_XContext *gfx, Gif_Stream *gfs, int i, Gif_XFrame *frames); int Gif_XAllocateColors(Gif_XContext *gfx, Gif_Colormap *gfcm); void Gif_XDeallocateColors(Gif_XContext *gfx, Gif_Colormap *gfcm); unsigned long * Gif_XClaimStreamColors(Gif_XContext *gfx, Gif_Stream *gfs, int *np_store); #ifdef __cplusplus } #endif #endif gifsicle-1.96/logo.gif000066400000000000000000000207771475770763400147260ustar00rootroot00000000000000GIF89a<{R{9֭ZέΌ{B{BJBΌ祵Rk1cBBRJ)kJscR{9BBBJ!Zc΄{RRƌ{)ZkccscZBRBZ)99JBJcR)Z1J! NETSCAPE2.0!2,<@pH,ȤrlD"E"άVzvK6 tx\&w/}9i{I}~wlO)f\ p{whp{wm^e`dpZ_[)atZt_^TNYmMNe-*#ޒF-T/A : ȐHEym$S|6eAQI>&I3 Q-- EdGSOC2jOU]3KDvbRe΋ pUbW"o CmZ1:[YEo t)6D4kŃuMGe08I X^ M֮s&D[ڞLΨ-?\vl~ãͺnޡy}W+VtUTztDUsNCd{]Vhtr~ww{vimzlξpؓwj_ǖpj{|laӦ,`7Ob"s-S:) vK?&\1zD'I&zc_t;H1*L}!qi &8MUTUUݗC, -&Tn (r;Q=ṠD[ݛ UZq9^-TNHX_3K_ٰ>p7O{uMz{'LWb41Xu2dG! ʱe`?`  REe,X Lm8F(9`  ɍk0PFNtp| @CIf 8&@٢jS# !! ,:dpH,Gbj:Ȩt8ЄAzOC.79 w}\89|OAvw^VM| !RL~|oeNSMzb!HN|pZEt qMCdifC ir{}tWt{sfᔯ }ñwt3vWo )A M ֊ Fex3?sle?HYYMp #7ý̹',D[qM8FFdD*R92k0Dswڅ*'HleKO'bùV1;%Ine u7sM9Ws|joqqk.%/Πy%tqF3̧0E f+}oQ"–9^L("[tw}&88t1w6xty*?ܜ{eM|]ҒI=x>(z6n:|~*'AA"qd9" q͆Yg ZGbpun*JӠ-]RX9IbwF,(sc!ȄX_@+A^HXgx+:heE)$4!6UfU5LAǖQZk1zE:) X8Nn.ԃ| lWWM2žF8၈iOH;!AbI6rr|Ĭ *; vW WʎD^z**w( , (cZIlp ;䖅]g>,0Ӝh J#:&RKZT@h:& +11 @$i& f. &:s7cWF!2,:dpH,HjX"tJM:XW f򚏢pm:;coxU>϶/Zz{.ca11WvwM RrlXcN10dWHb0XuDWWMC|.tlLY,}Ŀl/`yz`1dҟjcdNs胼젦kzmЊz1w*6M99ؚ4( VLb]sbp NaS*kd!ptPp&ir]CwH@{7ӧ G^8#⤴UDK&ٖ\9֖ۙ6movF9 c")jHIsROUF;Kutbq^(\}з+}a:cY4qX!_"6^vтG087)5Ԅd29$S\(x!@ GQD9I9gGӻH|_OMԶ^}v"oj!)E{.D%_[W-ޠ#WQY QW XWqxR/Ѳ$DR*֘!΁W|ȃF!B R#ǀ$R_Z  YTsEN^FE=ՖZTSV!F 4]4͐Xg UU܍$fmfYS #rQЗc^L PH8dP'{n71 $~JO .r΁B'FS7Vʱ~HPL! ,:dpH,mɤ%ШIJتvib{2B|*q|C]q9a4lk{\m&MGxsU&liL}eK}Vq[]UM'lnvZʌfǂ&L]ɝ'oḍh]͗KjڂT`r $}&#v&[^ b%mL{-TyǜWVݵ́I '%['g3]( 6o$n&C aWȥ#_I K9y0(f \[IjNBAqԕrHv@vcbd&4pDUafZse&:e^]΅>CFQbǩGU1WSY<駞*U4<@\@v]kϝڴwc]`Rq0ĢS*6.&H)D<&rֶݮ`jcƒ n$iff+=KDeؿ[U' W/̨ l܀m8QzŒr6o̙e'O<#j K/)Mcɜ]:#)PQ+f1bآZ2$ Y/"5%UJ.LR+s' 빽Xq6!K`+[ . tgRٳHnHP-A\kV4I"5񤺨٭~Gm>n(@ 7Lni3vNK6Z`77>@^/~@2dU!a_9 "PmMw^UDb%! ,:d di(AG{**4K|/ n+dh,"ʦJ=0gUyŖ ۭ s%s=*FB6~^wUXxP~jT^,oU^T}RtPFfMEQM.>P]=Fy--<@Ƹ5͸9AߡEB3.3F.8aOScpᭆ-6,xbDY\e 3< G*BVxd”4"4wmQ)<~/j$qh"p)ŘnB%x 8B+8J +)knQ"@PA*j ·p_XrBbdo,bo:j&sWHqɥΑYTyi<T_3hAURV <|']5A~}K! ,:d diD,p|tx>|,bTV̦3NgU&JV#+_sY$5iΏU}vG}RFo|?CNkxyJYJk^hf?@:-~<64EJDw·ĐI{<~GĦʳڠƗ>8Ld`- 0G 1K<E Ѣprp4hBJGF#eUQšu^alGPPK/Ʒ^Ѐ@nݞ*ۺB0JBY%8D4*qf怓e9.`6pk|YyU3D~栳mr7kIMw&LmsM#uCg~y;wg$;gifsicle-1.96/logo1.gif000066400000000000000000000014651475770763400150000ustar00rootroot00000000000000GIF89a0Dk1!s91s99{9BR{9R{Bc)kB{B1JBJJRBJZRZZck!k9sBR{RRcƔΌΥֵ!),0DpȤr<^8CbsJm>)Mu˵\DXLw`Syn[XdtycxCzUBb\Q  sCiG  )uTj}j av UXZ{Đg SQ"[uL¨I~KKFQ!uF=&)NIi6# 3{d &:$Fɪ'2E pNjlȄ`K,8qKC'a @'ꁦ NiR: Hb!Š"i 0`+7gPpeS gs(},Ĕo|7=фh"u x.xBcg}fH3!e'8@ Aρ }i \atĆ@݅u]vOQ[_@w~ `>p ]Y͵݂RL@~y 'o @8Otݑ~ F1JSR\vX;gifsicle-1.96/src/000077500000000000000000000000001475770763400140515ustar00rootroot00000000000000gifsicle-1.96/src/Makefile.am000066400000000000000000000016371475770763400161140ustar00rootroot00000000000000## Process this file with automake to produce Makefile.in AUTOMAKE_OPTIONS = foreign check-news bin_PROGRAMS = gifsicle @OTHERPROGRAMS@ EXTRA_PROGRAMS = gifview gifdiff LDADD = @LIBOBJS@ gifsicle_LDADD = $(LDADD) @GIFWRITE_O@ gifview_LDADD = $(LDADD) @X_LIBS@ @X_PRE_LIBS@ -lX11 @X_EXTRA_LIBS@ gifsicle_DEPENDENCIES = @GIFWRITE_O@ @LIBOBJS@ gifview_DEPENDENCIES = @LIBOBJS@ gifdiff_DEPENDENCIES = @LIBOBJS@ WERROR = @WERROR@ gifsicle_SOURCES = clp.c fmalloc.c \ giffunc.c gifread.c gifunopt.c \ gifsicle.h kcolor.h \ kcolor.c merge.c optimize.c quantize.c support.c xform.c \ gifsicle.c gifview_SOURCES = clp.c fmalloc.c \ giffunc.c gifread.c gifx.c \ gifview.c gifdiff_SOURCES = clp.c fmalloc.c \ giffunc.c gifread.c \ gifdiff.c AM_CPPFLAGS = $(X_CFLAGS) $(WERROR) -I$(top_srcdir)/include EXTRA_gifsicle_SOURCES = gifwrite.c ungifwrt.c EXTRA_DIST = opttemplate.c Makefile.bcc Makefile.w32 win32cfg.h gifsicle-1.96/src/Makefile.bcc000066400000000000000000000054141475770763400162430ustar00rootroot00000000000000# Win32 Makefile originally by Emil Mikulic # Updates by Eddie Kohler and # Steven Marthouse # ---------------------------------------------------------- # Makefile adapted for Borland C++ 5.5 Compiler # Stephen Schnipsel # # set this to your bcc directory! MYBCCDIR = D:\BCC55 # # ---------------------------------------------------------- # *** MAKING UNGIFSICLE *** # If `GIFWRITE_OBJ' is defined to `gifwrite.obj', Gifsicle will use # Unisys-patented LZW compression. If it is defined to `ungifwrt.obj', it # will use unpatented run-length compression, which creates larger GIFs but # is completely free software. If you downloaded the ungifsicle package, # which doesn't have `gifwrite.c', you MUST define `GIFWRITE_OBJ' to # `ungifwrt.obj' by commenting the first line below and uncommenting the # second. GIFWRITE_OBJ = gifwrite.obj #GIFWRITE_OBJ = ungifwrt.obj # *** SUPPORTING WILDCARD EXPANSION *** # Define `SETARGV_OBJ' to the filename for the `setargv.obj' object file. # The definition included here works for Microsoft compilers; you will # probably need to change it if you're using a different compiler. You can # define it to the empty string, in which case Gifsicle will compile fine, # but you won't be able to use wildcards in file name arguments. SETARGV_OBJ = $(MYBCCDIR)\LIB\WILDARGS.OBJ CC = bcc32 CFLAGS = -I.. -I..\INCLUDE -DHAVE_CONFIG_H=1 -D_CONSOLE -O2 -D_setmode=setmode GIFSICLE_OBJS = clp.obj fmalloc.obj giffunc.obj gifread.obj gifunopt.obj \ $(GIFWRITE_OBJ) merge.obj optimize.obj quantize.obj support.obj \ xform.obj gifsicle.obj $(SETARGV_OBJ) GIFDIFF_OBJS = clp.obj fmalloc.obj giffunc.obj gifread.obj gifdiff.obj \ $(SETARGV_OBJ) .c.obj: $(CC) $(CFLAGS) -c $< all: gifsicle.exe gifdiff.exe gifsicle.exe: $(GIFSICLE_OBJS) $(CC) $(CFLAGS) -egifsicle $(GIFSICLE_OBJS) gifdiff.exe: $(GIFDIFF_OBJS) $(CC) $(CFLAGS) -egifdiff $(GIFDIFF_OBJS) clp.obj: ..\config.h ..\include\lcdf\clp.h clp.c fmalloc.obj: ..\config.h fmalloc.c giffunc.obj: ..\config.h giffunc.c ..\include\lcdfgif\gif.h gifread.obj: ..\config.h gifread.c ..\include\lcdfgif\gif.h gifwrite.obj: ..\config.h gifwrite.c ..\include\lcdfgif\gif.h ungifwrt.obj: ..\config.h ungifwrt.c ..\include\lcdfgif\gif.h gifunopt.obj: ..\config.h gifunopt.c ..\include\lcdfgif\gif.h merge.obj: ..\config.h gifsicle.h merge.c optimize.obj: ..\config.h gifsicle.h optimize.c quantize.obj: ..\config.h gifsicle.h quantize.c support.obj: ..\config.h gifsicle.h support.c xform.obj: ..\config.h gifsicle.h xform.c gifsicle.obj: ..\config.h gifsicle.h gifsicle.c ..\config.h: win32cfg.h copy win32cfg.h ..\config.h clean: del *.obj del *.exe del *.tds gifsicle-1.96/src/Makefile.mingw000066400000000000000000000042651475770763400166400ustar00rootroot00000000000000# Win32 Makefile originally by Emil Mikulic # Updates by Eddie Kohler and # Steven Marthouse # ---------------------------------------------------------- # Makefile adapted for MinGW GCC Compiler # José Manuel Muñoz # ---------------------------------------------------------- # *** MAKING UNGIFSICLE *** # If `GIFWRITE_OBJ' is defined to `gifwrite.o', Gifsicle will use # Unisys-patented LZW compression. If it is defined to `ungifwrt.o', it # will use unpatented run-length compression, which creates larger GIFs but # is completely free software. If you downloaded the ungifsicle package, # which doesn't have `gifwrite.c', you MUST define `GIFWRITE_OBJ' to # `ungifwrt.o' by commenting the first line below and uncommenting the # second. GIFWRITE_OBJ = gifwrite.o #GIFWRITE_OBJ = ungifwrt.o # *** SUPPORTING WILDCARD EXPANSION *** # MinGW seems to include support for this by itself. CC = gcc MINGWFLAGS = -DHAVE_UINTPTR_T -DHAVE_INTTYPES_H CFLAGS = -I.. -I..\include -DHAVE_CONFIG_H=1 -D_CONSOLE -O2 $(MINGWFLAGS) GIFSICLE_OBJS = clp.o fmalloc.o giffunc.o gifread.o gifunopt.o \ $(GIFWRITE_OBJ) merge.o optimize.o quantize.o support.o \ xform.o gifsicle.o GIFDIFF_OBJS = clp.o fmalloc.o giffunc.o gifread.o gifdiff.o all: gifsicle.exe gifdiff.exe gifsicle.exe: $(GIFSICLE_OBJS) $(CC) $(CFLAGS) -o $@ $(GIFSICLE_OBJS) gifdiff.exe: $(GIFDIFF_OBJS) $(CC) $(CFLAGS) -o $@ $(GIFDIFF_OBJS) clp.o: ..\config.h ..\include\lcdf\clp.h clp.c fmalloc.o: ..\config.h fmalloc.c giffunc.o: ..\config.h giffunc.c ..\include\lcdfgif\gif.h gifread.o: ..\config.h gifread.c ..\include\lcdfgif\gif.h gifwrite.o: ..\config.h gifwrite.c ..\include\lcdfgif\gif.h ungifwrt.o: ..\config.h ungifwrt.c ..\include\lcdfgif\gif.h gifunopt.o: ..\config.h gifunopt.c ..\include\lcdfgif\gif.h merge.o: ..\config.h gifsicle.h merge.c optimize.o: ..\config.h gifsicle.h optimize.c quantize.o: ..\config.h gifsicle.h quantize.c support.o: ..\config.h gifsicle.h support.c xform.o: ..\config.h gifsicle.h xform.c gifsicle.o: ..\config.h gifsicle.h gifsicle.c ..\config.h: win32cfg.h copy win32cfg.h ..\config.h clean: del *.o del *.exe gifsicle-1.96/src/Makefile.w32000066400000000000000000000042551475770763400161310ustar00rootroot00000000000000# Win32 Makefile originally by Emil Mikulic # Updates by Eddie Kohler and # Steven Marthouse # This makefile should work under Win32 (95/98/NT/whatever) or Win64. # It should work out-of-the-box with Visual C++ 5. # # C:\GIFSICLE> nmake -f Makefile.w32 # *** MAKING UNGIFSICLE *** # If `GIFWRITE_OBJ' is defined to `gifwrite.obj', Gifsicle will use # Unisys-patented LZW compression. If it is defined to `ungifwrt.obj', it # will use unpatented run-length compression, which creates larger GIFs but # is completely free software. If you downloaded the ungifsicle package, # which doesn't have `gifwrite.c', you MUST define `GIFWRITE_OBJ' to # `ungifwrt.obj' by commenting the first line below and uncommenting the # second. GIFWRITE_OBJ = gifwrite.obj #GIFWRITE_OBJ = ungifwrt.obj CC = cl CFLAGS = -I.. -I..\include -DHAVE_CONFIG_H -D_CONSOLE /W2 /O2 LDLIBS = setargv.obj GIFSICLE_OBJS = clp.obj fmalloc.obj giffunc.obj gifread.obj gifunopt.obj \ $(GIFWRITE_OBJ) merge.obj optimize.obj quantize.obj support.obj \ xform.obj gifsicle.obj GIFDIFF_OBJS = clp.obj fmalloc.obj giffunc.obj gifread.obj gifdiff.obj .c.obj: $(CC) $(CFLAGS) /c $< gifsicle.exe: $(GIFSICLE_OBJS) $(CC) $(CFLAGS) /Fegifsicle.exe $(GIFSICLE_OBJS) /link $(LDLIBS) gifdiff.exe: $(GIFDIFF_OBJS) $(CC) $(CFLAGS) /Fegifdiff.exe $(GIFDIFF_OBJS) /link $(LDLIBS) clp.obj: ..\config.h ..\include\lcdf\clp.h clp.c fmalloc.obj: ..\config.h fmalloc.c giffunc.obj: ..\config.h giffunc.c ..\include\lcdfgif\gif.h gifread.obj: ..\config.h gifread.c ..\include\lcdfgif\gif.h gifwrite.obj: ..\config.h gifwrite.c ..\include\lcdfgif\gif.h ungifwrt.obj: ..\config.h ungifwrt.c ..\include\lcdfgif\gif.h gifunopt.obj: ..\config.h gifunopt.c ..\include\lcdfgif\gif.h merge.obj: ..\config.h gifsicle.h merge.c optimize.obj: ..\config.h gifsicle.h optimize.c quantize.obj: ..\config.h gifsicle.h quantize.c support.obj: ..\config.h gifsicle.h support.c xform.obj: ..\config.h gifsicle.h xform.c gifsicle.obj: ..\config.h gifsicle.h gifsicle.c ..\config.h: win32cfg.h copy win32cfg.h ..\config.h clean: del *.obj del *.exe gifsicle-1.96/src/clp.c000066400000000000000000002324421475770763400150020ustar00rootroot00000000000000/* -*- related-file-name: "../include/lcdf/clp.h" -*- */ /* clp.c - Complete source code for CLP. * This file is part of CLP, the command line parser package. * * Copyright (c) 1997-2025 Eddie Kohler, ekohler@gmail.com * * CLP is free software. It is distributed under the GNU General Public * License, Version 2, or, alternatively and at your discretion, under the * more permissive (BSD-like) Click LICENSE file as described below. * * 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, subject to the * conditions listed in the Click LICENSE file, which is available in full at * http://github.com/kohler/click/blob/master/LICENSE. The conditions * include: you must preserve this copyright notice, and you cannot mention * the copyright holders in advertising related to the Software without * their permission. The Software is provided WITHOUT ANY WARRANTY, EXPRESS * OR IMPLIED. This notice is a summary of the Click LICENSE file; the * license in that file is binding. */ #if HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #if HAVE_SYS_TYPES_H # include #endif #if HAVE_INTTYPES_H || !defined(HAVE_CONFIG_H) # include #endif /* By default, assume we have inttypes.h, strtoul, and uintptr_t. */ #if !defined(HAVE_STRTOUL) && !defined(HAVE_CONFIG_H) # define HAVE_STRTOUL 1 #endif #if defined(HAVE_INTTYPES_H) || !defined(HAVE_CONFIG_H) # include #endif #if !defined(HAVE_UINTPTR_T) && defined(HAVE_CONFIG_H) typedef unsigned long uintptr_t; #endif #ifdef __cplusplus extern "C" { #endif /** @file clp.h * @brief Functions for parsing command line options. * * The CLP functions are used to parse command line arguments into options. * It automatically handles value parsing, error messages, long options with * minimum prefix matching, short options, and negated options. * * The CLP model works like this. * *
    *
  1. The user declares an array of Clp_Option structures that define the * options their program accepts.
  2. *
  3. The user creates a Clp_Parser object using Clp_NewParser(), passing in * the command line arguments to parse and the Clp_Option structures.
  4. *
  5. A loop repeatedly calls Clp_Next() to parse the arguments.
  6. *
* * Unlike many command line parsing libraries, CLP steps through all arguments * one at a time, rather than slurping up all options at once. This makes it * meaningful to give an option more than once. * * Here's an example. * * @code * #define ANIMAL_OPT 1 * #define VEGETABLE_OPT 2 * #define MINERALS_OPT 3 * #define USAGE_OPT 4 * * static const Clp_Option options[] = { * { "animal", 'a', ANIMAL_OPT, Clp_ValString, 0 }, * { "vegetable", 'v', VEGETABLE_OPT, Clp_ValString, Clp_Negate | Clp_Optional }, * { "minerals", 'm', MINERALS_OPT, Clp_ValInt, 0 }, * { "usage", 0, USAGE_OPT, 0, 0 } * }; * * int main(int argc, char *argv[]) { * Clp_Parser *clp = Clp_NewParser(argc, argv, * sizeof(options) / sizeof(options[0]), options); * int opt; * while ((opt = Clp_Next(clp)) != Clp_Done) * switch (opt) { * case ANIMAL_OPT: * fprintf(stderr, "animal is %s\n", clp->val.s); * break; * case VEGETABLE_OPT: * if (clp->negated) * fprintf(stderr, "no vegetables!\n"); * else if (clp->have_val) * fprintf(stderr, "vegetable is %s\n", clp->val.s); * else * fprintf(stderr, "vegetables OK\n"); * break; * case MINERALS_OPT: * fprintf(stderr, "%d minerals\n", clp->val.i); * break; * case USAGE_OPT: * fprintf(stderr, "Usage: 20q [--animal=ANIMAL] [--vegetable[=VEGETABLE]] [--minerals=N]\n"); * break; * case Clp_NotOption: * fprintf(stderr, "non-option %s\n", clp->vstr); * break; * } * } * } * @endcode * * Here are a couple of executions. * *
 * % ./20q --animal=cat
 * animal is cat
 * % ./20q --animal=cat -a dog -afish --animal bird --an=snake
 * animal is cat
 * animal is dog
 * animal is fish
 * animal is bird
 * animal is snake
 * % ./20q --no-vegetables
 * no vegetables!
 * % ./20q -v
 * vegetables OK
 * % ./20q -vkale
 * vegetable is kale
 * % ./20q -m10
 * 10 minerals
 * % ./20q -m foo
 * '-m' expects an integer, not 'foo'
 * 
*/ /* Option types for Clp_SetOptionChar */ #define Clp_DoubledLong (Clp_LongImplicit * 2) #define Clp_InitialValType 8 #define MAX_AMBIGUOUS_VALUES 4 typedef struct { int val_type; Clp_ValParseFunc func; int flags; void *user_data; } Clp_ValType; typedef struct { unsigned ilong : 1; unsigned ishort : 1; unsigned imandatory : 1; unsigned ioptional : 1; unsigned ipos : 1; unsigned ineg : 1; unsigned iprefmatch : 1; unsigned lmmpos_short : 1; unsigned lmmneg_short : 1; unsigned unquoted : 1; unsigned char ilongoff; int lmmpos; int lmmneg; } Clp_InternOption; #define Clp_OptionCharsSize 5 typedef struct { int c; int type; } Clp_Oclass; #define Clp_OclassSize 10 typedef struct Clp_Internal { const Clp_Option *opt; Clp_InternOption *iopt; int nopt; unsigned opt_generation; Clp_ValType *valtype; int nvaltype; const char * const *argv; int argc; Clp_Oclass oclass[Clp_OclassSize]; int noclass; int long1pos; int long1neg; int utf8; char option_chars[Clp_OptionCharsSize]; const char *xtext; const char *program_name; void (*error_handler)(Clp_Parser *, const char *); int option_processing; int current_option; unsigned char is_short; unsigned char whole_negated; /* true if negated by an option character */ unsigned char could_be_short; unsigned char current_short; unsigned char negated_by_no; int ambiguous; int ambiguous_values[MAX_AMBIGUOUS_VALUES]; } Clp_Internal; struct Clp_ParserState { const char * const *argv; int argc; char option_chars[Clp_OptionCharsSize]; const char *xtext; int option_processing; unsigned opt_generation; int current_option; unsigned char is_short; unsigned char whole_negated; unsigned char current_short; unsigned char negated_by_no; }; typedef struct Clp_StringList { Clp_Option *items; Clp_InternOption *iopt; int nitems; unsigned char allow_int; unsigned char val_long; int nitems_invalid_report; } Clp_StringList; static const Clp_Option clp_option_sentinel[] = { {"", 0, Clp_NotOption, 0, 0}, {"", 0, Clp_Done, 0, 0}, {"", 0, Clp_BadOption, 0, 0}, {"", 0, Clp_Error, 0, 0} }; static int parse_string(Clp_Parser *, const char *, int, void *); static int parse_int(Clp_Parser *, const char *, int, void *); static int parse_bool(Clp_Parser *, const char *, int, void *); static int parse_double(Clp_Parser *, const char *, int, void *); static int parse_string_list(Clp_Parser *, const char *, int, void *); static int ambiguity_error(Clp_Parser *, int, int *, const Clp_Option *, const Clp_InternOption *, const char *, const char *, ...); /******* * utf8 **/ #define U_REPLACEMENT 0xFFFD static char * encode_utf8(char *s, int n, int c) { if (c < 0 || c >= 0x110000 || (c >= 0xD800 && c <= 0xDFFF)) c = U_REPLACEMENT; if (c <= 0x7F && n >= 1) *s++ = c; else if (c <= 0x7FF && n >= 2) { *s++ = 0xC0 | (c >> 6); goto char1; } else if (c <= 0xFFFF && n >= 3) { *s++ = 0xE0 | (c >> 12); goto char2; } else if (n >= 4) { *s++ = 0xF0 | (c >> 18); *s++ = 0x80 | ((c >> 12) & 0x3F); char2: *s++ = 0x80 | ((c >> 6) & 0x3F); char1: *s++ = 0x80 | (c & 0x3F); } return s; } static int decode_utf8(const char *s, const char **cp) { int c; if ((unsigned char) *s <= 0x7F) /* 1 byte: 0x000000-0x00007F */ c = *s++; else if ((unsigned char) *s <= 0xC1) /* bad/overlong encoding */ goto replacement; else if ((unsigned char) *s <= 0xDF) { /* 2 bytes: 0x000080-0x0007FF */ if ((s[1] & 0xC0) != 0x80) /* bad encoding */ goto replacement; c = (*s++ & 0x1F) << 6; goto char1; } else if ((unsigned char) *s <= 0xEF) { /* 3 bytes: 0x000800-0x00FFFF */ if ((s[1] & 0xC0) != 0x80 /* bad encoding */ || (s[2] & 0xC0) != 0x80 /* bad encoding */ || ((unsigned char) *s == 0xE0 /* overlong encoding */ && (s[1] & 0xE0) == 0x80) || ((unsigned char) *s == 0xED /* encoded surrogate */ && (s[1] & 0xE0) == 0xA0)) goto replacement; c = (*s++ & 0x0F) << 12; goto char2; } else if ((unsigned char) *s <= 0xF4) { /* 4 bytes: 0x010000-0x10FFFF */ if ((s[1] & 0xC0) != 0x80 /* bad encoding */ || (s[2] & 0xC0) != 0x80 /* bad encoding */ || (s[3] & 0xC0) != 0x80 /* bad encoding */ || ((unsigned char) *s == 0xF0 /* overlong encoding */ && (s[1] & 0xF0) == 0x80) || ((unsigned char) *s == 0xF4 /* encoded value > 0x10FFFF */ && (unsigned char) s[1] >= 0x90)) goto replacement; c = (*s++ & 0x07) << 18; c += (*s++ & 0x3F) << 12; char2: c += (*s++ & 0x3F) << 6; char1: c += (*s++ & 0x3F); } else { replacement: c = U_REPLACEMENT; for (s++; (*s & 0xC0) == 0x80; s++) /* nothing */; } if (cp) *cp = s; return c; } static int utf8_charlen(const char *s) { const char *sout; (void) decode_utf8(s, &sout); return sout - s; } static int clp_utf8_charlen(const Clp_Internal *cli, const char *s) { return (cli->utf8 ? utf8_charlen(s) : 1); } /******* * Clp_NewParser, etc. **/ static int min_different_chars(const char *s, const char *t) /* Returns the minimum number of bytes required to distinguish s from t. If s is shorter than t, returns strlen(s). */ { const char *sfirst = s; while (*s && *t && *s == *t) s++, t++; if (!*s) return s - sfirst; else return s - sfirst + 1; } static int long_as_short(const Clp_Internal *cli, const Clp_Option *o, Clp_InternOption *io, int failure) { if ((cli->long1pos || cli->long1neg) && io->ilong) { const char *name = o->long_name + io->ilongoff; if (cli->utf8) { int c = decode_utf8(name, &name); if (!*name && c && c != U_REPLACEMENT) return c; } else if (name[0] && !name[1]) return (unsigned char) name[0]; } return failure; } static void compare_options(Clp_Parser *clp, const Clp_Option *o1, Clp_InternOption *io1, const Clp_Option *o2, Clp_InternOption *io2) { Clp_Internal *cli = clp->internal; int short1, shortx1; /* ignore meaningless combinations */ if ((!io1->ishort && !io1->ilong) || (!io2->ishort && !io2->ilong) || !((io1->ipos && io2->ipos) || (io1->ineg && io2->ineg)) || o1->option_id == o2->option_id) return; /* look for duplication of short options */ short1 = (io1->ishort ? o1->short_name : -1); shortx1 = long_as_short(cli, o1, io1, -2); if (short1 >= 0 || shortx1 >= 0) { int short2 = (io2->ishort ? o2->short_name : -3); int shortx2 = long_as_short(cli, o2, io2, -4); if (short1 == short2) Clp_OptionError(clp, "CLP internal error: more than 1 option has short name %<%c%>", short1); else if ((short1 == shortx2 || shortx1 == short2 || shortx1 == shortx2) && ((io1->ipos && io2->ipos && cli->long1pos) || (io1->ineg && io2->ineg && cli->long1neg))) Clp_OptionError(clp, "CLP internal error: 1-char long name conflicts with short name %<%c%>", (short1 == shortx2 ? shortx2 : shortx1)); } /* analyze longest minimum match */ if (io1->ilong) { const char *name1 = o1->long_name + io1->ilongoff; /* long name's first character matches short name */ if (io2->ishort && !io1->iprefmatch) { int name1char = (cli->utf8 ? decode_utf8(name1, 0) : (unsigned char) *name1); if (name1char == o2->short_name) { if (io1->ipos && io2->ipos) io1->lmmpos_short = 1; if (io1->ineg && io2->ineg) io1->lmmneg_short = 1; } } /* match long name to long name */ if (io2->ilong) { const char *name2 = o2->long_name + io2->ilongoff; if (strcmp(name1, name2) == 0) Clp_OptionError(clp, "CLP internal error: duplicate long name %<%s%>", name1); if (io1->ipos && io2->ipos && !strncmp(name1, name2, io1->lmmpos) && (!io1->iprefmatch || strncmp(name1, name2, strlen(name1)))) io1->lmmpos = min_different_chars(name1, name2); if (io1->ineg && io2->ineg && !strncmp(name1, name2, io1->lmmneg) && (!io1->iprefmatch || strncmp(name1, name2, strlen(name1)))) io1->lmmneg = min_different_chars(name1, name2); } } } static void calculate_lmm(Clp_Parser *clp, const Clp_Option *opt, Clp_InternOption *iopt, int nopt) { int i, j; for (i = 0; i < nopt; ++i) { iopt[i].lmmpos = iopt[i].lmmneg = 1; iopt[i].lmmpos_short = iopt[i].lmmneg_short = 0; for (j = 0; j < nopt; ++j) compare_options(clp, &opt[i], &iopt[i], &opt[j], &iopt[j]); } } /** @param argc number of arguments * @param argv argument array * @param nopt number of option definitions * @param opt option definition array * @return the parser * * The new Clp_Parser that will parse the arguments in @a argv according to * the option definitions in @a opt. * * The Clp_Parser is created with the following characteristics: * *
    *
  • The "-" character introduces short options (Clp_SetOptionChar(clp, * '-', Clp_Short)).
  • *
  • Clp_ProgramName is set from the first argument in @a argv, if any. The * first argument returned by Clp_Next() will be the second argument in @a * argv. Note that this behavior differs from Clp_SetArguments.
  • *
  • UTF-8 support is on iff the LANG environment variable contains * one of the substrings "UTF-8", "UTF8", or "utf8". Override this with * Clp_SetUTF8().
  • *
  • The Clp_ValString, Clp_ValStringNotOption, Clp_ValInt, Clp_ValUnsigned, * Clp_ValLong, Clp_ValUnsignedLong, Clp_ValBool, and Clp_ValDouble types are * installed.
  • *
  • Errors are reported to standard error.
  • *
* * You may also create a Clp_Parser with no arguments or options * (Clp_NewParser(0, 0, 0, 0)) and set the arguments and options * later. * * Returns NULL if there isn't enough memory to construct the parser. * * @note The CLP library will not modify the contents of @a argv or @a opt. * The calling program must not modify @a opt. It may modify @a argv in * limited cases. */ Clp_Parser * Clp_NewParser(int argc, const char * const *argv, int nopt, const Clp_Option *opt) { Clp_Parser *clp = (Clp_Parser *)malloc(sizeof(Clp_Parser)); Clp_Internal *cli = (Clp_Internal *)malloc(sizeof(Clp_Internal)); Clp_InternOption *iopt = (Clp_InternOption *)malloc(sizeof(Clp_InternOption) * nopt); if (cli) cli->valtype = (Clp_ValType *)malloc(sizeof(Clp_ValType) * Clp_InitialValType); if (!clp || !cli || !iopt || !cli->valtype) goto failed; clp->option = &clp_option_sentinel[-Clp_Done]; clp->negated = 0; clp->have_val = 0; clp->vstr = 0; clp->user_data = 0; clp->internal = cli; cli->opt = opt; cli->nopt = nopt; cli->iopt = iopt; cli->opt_generation = 0; cli->error_handler = 0; /* Assign program name (now so we can call Clp_OptionError) */ if (argc > 0) { const char *slash = strrchr(argv[0], '/'); cli->program_name = slash ? slash + 1 : argv[0]; } else cli->program_name = 0; /* Assign arguments, skipping program name */ Clp_SetArguments(clp, argc - 1, argv + 1); /* Initialize UTF-8 status and option classes */ { char *s = getenv("LANG"); cli->utf8 = (s && (strstr(s, "UTF-8") != 0 || strstr(s, "UTF8") != 0 || strstr(s, "utf8") != 0)); } cli->oclass[0].c = '-'; cli->oclass[0].type = Clp_Short; cli->noclass = 1; cli->long1pos = cli->long1neg = 0; /* Add default type parsers */ cli->nvaltype = 0; Clp_AddType(clp, Clp_ValString, 0, parse_string, 0); Clp_AddType(clp, Clp_ValStringNotOption, Clp_DisallowOptions, parse_string, 0); Clp_AddType(clp, Clp_ValInt, 0, parse_int, (void*) (uintptr_t) 0); Clp_AddType(clp, Clp_ValUnsigned, 0, parse_int, (void*) (uintptr_t) 1); Clp_AddType(clp, Clp_ValLong, 0, parse_int, (void*) (uintptr_t) 2); Clp_AddType(clp, Clp_ValUnsignedLong, 0, parse_int, (void*) (uintptr_t) 3); Clp_AddType(clp, Clp_ValBool, 0, parse_bool, 0); Clp_AddType(clp, Clp_ValDouble, 0, parse_double, 0); /* Set options */ Clp_SetOptions(clp, nopt, opt); return clp; failed: if (cli && cli->valtype) free(cli->valtype); if (cli) free(cli); if (clp) free(clp); if (iopt) free(iopt); return 0; } /** @param clp the parser * * All memory associated with @a clp is freed. */ void Clp_DeleteParser(Clp_Parser *clp) { int i; Clp_Internal *cli; if (!clp) return; cli = clp->internal; /* get rid of any string list types */ for (i = 0; i < cli->nvaltype; i++) if (cli->valtype[i].func == parse_string_list) { Clp_StringList *clsl = (Clp_StringList *)cli->valtype[i].user_data; free(clsl->items); free(clsl->iopt); free(clsl); } free(cli->valtype); free(cli->iopt); free(cli); free(clp); } /** @param clp the parser * @param errh error handler function * @return previous error handler function * * The error handler function is called when CLP encounters an error while * parsing the command line. It is called with the arguments "(*errh)(@a * clp, s)", where s is a description of the error terminated by * a newline. The s descriptions produced by CLP itself are prefixed * by the program name, if any. */ Clp_ErrorHandler Clp_SetErrorHandler(Clp_Parser *clp, void (*errh)(Clp_Parser *, const char *)) { Clp_Internal *cli = clp->internal; Clp_ErrorHandler old = cli->error_handler; cli->error_handler = errh; return old; } /** @param clp the parser * @param utf8 does the parser support UTF-8? * @return previous UTF-8 mode * * In UTF-8 mode, all input strings (arguments and long names for options) are * assumed to be encoded via UTF-8, and all character names * (Clp_SetOptionChar() and short names for options) may cover the whole * Unicode range. Out of UTF-8 mode, all input strings are treated as binary, * and all character names must be unsigned char values. * * Furthermore, error messages in UTF-8 mode may contain Unicode quote * characters. */ int Clp_SetUTF8(Clp_Parser *clp, int utf8) { Clp_Internal *cli = clp->internal; int old_utf8 = cli->utf8; cli->utf8 = utf8; calculate_lmm(clp, cli->opt, cli->iopt, cli->nopt); return old_utf8; } /** @param clp the parser * @param c character * @return option character treatment * * Returns an integer specifying how CLP treats arguments that begin * with character @a c. See Clp_SetOptionChar for possibilities. */ int Clp_OptionChar(Clp_Parser *clp, int c) { Clp_Internal *cli = clp->internal; int i, oclass = 0; if (cli->noclass > 0 && cli->oclass[0].c == 0) oclass = cli->oclass[0].type; for (i = 0; i < cli->noclass; ++i) if (cli->oclass[i].c == c) oclass = cli->oclass[i].type; return oclass; } /** @param clp the parser * @param c character * @param type option character treatment * @return previous option character treatment, or -1 on error * * @a type specifies how CLP treats arguments that begin with character @a c. * Possibilities are: * *
*
Clp_NotOption (or 0)
*
The argument cannot be an option.
*
Clp_Long
*
The argument is a long option.
*
Clp_Short
*
The argument is a set of short options.
*
Clp_Short|Clp_Long
*
The argument is either a long option or, if no matching long option is * found, a set of short options.
*
Clp_LongNegated
*
The argument is a negated long option. For example, after * Clp_SetOptionChar(@a clp, '^', Clp_LongNegated), the argument "^foo" is * equivalent to "--no-foo".
*
Clp_ShortNegated
*
The argument is a set of negated short options.
*
Clp_ShortNegated|Clp_LongNegated
*
The argument is either a negated long option or, if no matching long * option is found, a set of negated short options.
*
Clp_LongImplicit
*
The argument may be a long option, where the character @a c is actually * part of the long option name. For example, after Clp_SetOptionChar(@a clp, * 'f', Clp_LongImplicit), the argument "foo" may be equivalent to * "--foo".
*
* * In UTF-8 mode, @a c may be any Unicode character. Otherwise, @a c must be * an unsigned char value. The special character 0 assigns @a type to @em * every character. * * It is an error if @a c is out of range, @a type is illegal, or there are * too many character definitions stored in @a clp already. The function * returns -1 on error. * * A double hyphen "--" always introduces a long option. This behavior cannot * currently be changed with Clp_SetOptionChar(). */ int Clp_SetOptionChar(Clp_Parser *clp, int c, int type) { int i, long1pos, long1neg; int old = Clp_OptionChar(clp, c); Clp_Internal *cli = clp->internal; if (type != Clp_NotOption && type != Clp_Short && type != Clp_Long && type != Clp_ShortNegated && type != Clp_LongNegated && type != Clp_LongImplicit && type != (Clp_Short | Clp_Long) && type != (Clp_ShortNegated | Clp_LongNegated)) return -1; if (c < 0 || c >= (cli->utf8 ? 0x110000 : 256)) return -1; if (c == 0) cli->noclass = 0; for (i = 0; i < cli->noclass; ++i) if (cli->oclass[i].c == c) break; if (i == Clp_OclassSize) return -1; cli->oclass[i].c = c; cli->oclass[i].type = type; if (cli->noclass == i) cli->noclass = i + 1; long1pos = long1neg = 0; for (i = 0; i < cli->noclass; ++i) { if ((cli->oclass[i].type & Clp_Short) && (cli->oclass[i].type & Clp_Long)) long1pos = 1; if ((cli->oclass[i].type & Clp_ShortNegated) && (cli->oclass[i].type & Clp_LongNegated)) long1neg = 1; } if (long1pos != cli->long1pos || long1neg != cli->long1neg) { /* Must recheck option set */ cli->long1pos = long1pos; cli->long1neg = long1neg; calculate_lmm(clp, cli->opt, cli->iopt, cli->nopt); } return old; } /** @param clp the parser * @param nopt number of option definitions * @param opt option definition array * @return 0 on success, -1 on failure * * Installs the option definitions in @a opt. Future option parsing will * use @a opt to search for options. * * Also checks @a opt's option definitions for validity. "CLP internal * errors" are reported via Clp_OptionError() if: * *
    *
  • An option has a negative ID.
  • *
  • Two different short options have the same name.
  • *
  • Two different long options have the same name.
  • *
  • A short and a long option are ambiguous, in that some option character * might introduce either a short or a long option (e.g., Clp_SetOptionChar(@a * clp, '-', Clp_Long|Clp_Short)), and a short name equals a long name.
  • *
* * If necessary memory cannot be allocated, this function returns -1 without * modifying the parser. * * @note The CLP library will not modify the contents of @a argv or @a opt. * The calling program must not modify @a opt either until another call to * Clp_SetOptions() or the parser is destroyed. */ int Clp_SetOptions(Clp_Parser *clp, int nopt, const Clp_Option *opt) { Clp_Internal *cli = clp->internal; Clp_InternOption *iopt; int i; static unsigned opt_generation = 0; if (nopt > cli->nopt) { iopt = (Clp_InternOption *)malloc(sizeof(Clp_InternOption) * nopt); if (!iopt) return -1; free(cli->iopt); cli->iopt = iopt; } cli->opt = opt; cli->nopt = nopt; cli->opt_generation = ++opt_generation; iopt = cli->iopt; cli->current_option = -1; /* Massage the options to make them usable */ for (i = 0; i < nopt; ++i) { memset(&iopt[i], 0, sizeof(iopt[i])); /* Ignore negative option_ids, which are internal to CLP */ if (opt[i].option_id < 0) { Clp_OptionError(clp, "CLP internal error: option %d has negative option_id", i); iopt[i].ilong = iopt[i].ishort = iopt[i].ipos = iopt[i].ineg = iopt[i].unquoted = 0; continue; } /* Set flags based on input flags */ iopt[i].ilong = (opt[i].long_name != 0 && opt[i].long_name[0] != 0); iopt[i].ishort = (opt[i].short_name > 0 && opt[i].short_name < (cli->utf8 ? 0x110000 : 256)); iopt[i].ipos = 1; iopt[i].ineg = (opt[i].flags & Clp_Negate) != 0; iopt[i].imandatory = (opt[i].flags & Clp_Mandatory) != 0; iopt[i].ioptional = (opt[i].flags & Clp_Optional) != 0; iopt[i].iprefmatch = (opt[i].flags & Clp_PreferredMatch) != 0; iopt[i].unquoted = 0; iopt[i].ilongoff = 0; /* Enforce invariants */ if (opt[i].val_type <= 0) iopt[i].imandatory = iopt[i].ioptional = 0; if (opt[i].val_type > 0 && !iopt[i].ioptional) iopt[i].imandatory = 1; /* Options that start with 'no-' should be changed to OnlyNegated */ if (iopt[i].ilong && strncmp(opt[i].long_name, "no-", 3) == 0) { iopt[i].ipos = 0; iopt[i].ineg = 1; iopt[i].ilongoff = 3; if (strncmp(opt[i].long_name + 3, "no-", 3) == 0) Clp_OptionError(clp, "CLP internal error: option %d begins with \"no-no-\"", i); } else if (opt[i].flags & Clp_OnlyNegated) { iopt[i].ipos = 0; iopt[i].ineg = 1; } } /* Check option set */ calculate_lmm(clp, opt, iopt, nopt); return 0; } /** @param clp the parser * @param argc number of arguments * @param argv argument array * * Installs the arguments in @a argv for parsing. Future option parsing will * analyze @a argv. * * Unlike Clp_NewParser(), this function does not treat @a argv[0] specially. * The first subsequent call to Clp_Next() will analyze @a argv[0]. * * This function also sets option processing to on, as by * Clp_SetOptionProcessing(@a clp, 1). * * @note The CLP library will not modify the contents of @a argv. The calling * program should not generally modify the element of @a argv that CLP is * currently analyzing. */ void Clp_SetArguments(Clp_Parser *clp, int argc, const char * const *argv) { Clp_Internal *cli = clp->internal; cli->argc = argc + 1; cli->argv = argv - 1; cli->is_short = 0; cli->whole_negated = 0; cli->option_processing = 1; cli->current_option = -1; } /** @param clp the parser * @param on whether to search for options * @return previous option processing setting * * When option processing is off, every call to Clp_Next() returns * Clp_NotOption. By default the option "--" turns off option * processing and is otherwise ignored. */ int Clp_SetOptionProcessing(Clp_Parser *clp, int on) { Clp_Internal *cli = clp->internal; int old = cli->option_processing; cli->option_processing = on; return old; } /******* * functions for Clp_Option lists **/ /* the ever-glorious argcmp */ static int argcmp(const char *ref, const char *arg, int min_match, int fewer_dashes) /* Returns 0 if ref and arg don't match. Returns -1 if ref and arg match, but fewer than min_match characters. Returns len if ref and arg match min_match or more characters; len is the number of characters that matched in arg. Allows arg to contain fewer dashes than ref iff fewer_dashes != 0. Examples: argcmp("x", "y", 1, 0) --> 0 / just plain wrong argcmp("a", "ax", 1, 0) --> 0 / ...even though min_match == 1 and the 1st chars match argcmp("box", "bo", 3, 0) --> -1 / ambiguous argcmp("cat", "c=3", 1, 0) --> 1 / handles = arguments */ { const char *refstart = ref; const char *argstart = arg; assert(min_match > 0); compare: while (*ref && *arg && *arg != '=' && *ref == *arg) ref++, arg++; /* Allow arg to contain fewer dashes than ref */ if (fewer_dashes && *ref == '-' && ref[1] && ref[1] == *arg) { ref++; goto compare; } if (*arg && *arg != '=') return 0; else if (ref - refstart < min_match) return -1; else return arg - argstart; } static int find_prefix_opt(Clp_Parser *clp, const char *arg, int nopt, const Clp_Option *opt, const Clp_InternOption *iopt, int *ambiguous, int *ambiguous_values) /* Looks for an unambiguous match of 'arg' against one of the long options in 'opt'. Returns positive if it finds one; otherwise, returns -1 and possibly changes 'ambiguous' and 'ambiguous_values' to keep track of at most MAX_AMBIGUOUS_VALUES possibilities. */ { int i, fewer_dashes = 0, first_ambiguous = *ambiguous; int negated = clp && clp->negated; int first_charlen = (clp ? clp_utf8_charlen(clp->internal, arg) : 1); retry: for (i = 0; i < nopt; i++) { int len, lmm; if (!iopt[i].ilong || (negated ? !iopt[i].ineg : !iopt[i].ipos)) continue; lmm = (negated ? iopt[i].lmmneg : iopt[i].lmmpos); if (clp && clp->internal->could_be_short && (negated ? iopt[i].lmmneg_short : iopt[i].lmmpos_short)) lmm = (first_charlen >= lmm ? first_charlen + 1 : lmm); len = argcmp(opt[i].long_name + iopt[i].ilongoff, arg, lmm, fewer_dashes); if (len > 0) return i; else if (len < 0) { if (*ambiguous < MAX_AMBIGUOUS_VALUES) ambiguous_values[*ambiguous] = i; ++(*ambiguous); } } /* If there were no partial matches, try again with fewer_dashes true */ if (*ambiguous == first_ambiguous && !fewer_dashes) { fewer_dashes = 1; goto retry; } return -1; } /***** * Argument parsing **/ static int val_type_binsearch(Clp_Internal *cli, int val_type) { unsigned l = 0, r = cli->nvaltype; while (l < r) { unsigned m = l + (r - l) / 2; if (cli->valtype[m].val_type == val_type) return m; else if (cli->valtype[m].val_type < val_type) l = m + 1; else r = m; } return l; } /** @param clp the parser * @param val_type value type ID * @param flags value type flags * @param parser parser function * @param user_data user data for @a parser function * @return 0 on success, -1 on failure * * Defines argument type @a val_type in parser @a clp. The parsing function * @a parser will be passed argument values for type @a val_type. It should * parse the argument into values (usually in @a clp->val, but sometimes * elsewhere), report errors if necessary, and return whether the parse was * successful. * * Any prior argument parser match @a val_type is removed. @a val_type must * be greater than zero. * * @a flags specifies additional parsing flags. At the moment the only * relevant flag is Clp_DisallowOptions, which means that separated values * must not look like options. For example, assume argument * --a/-a has mandatory value type Clp_ValStringNotOption * (which has Clp_DisallowOptions). Then: * *
    *
  • --a=--b will parse with value --b.
  • *
  • -a--b will parse with value --b.
  • *
  • --a --b will not parse, since the mandatory value looks like * an option.
  • *
  • -a --b will not parse, since the mandatory value looks like * an option.
  • *
*/ int Clp_AddType(Clp_Parser *clp, int val_type, int flags, Clp_ValParseFunc parser, void *user_data) { Clp_Internal *cli = clp->internal; int vtpos; if (val_type <= 0 || !parser) return -1; vtpos = val_type_binsearch(cli, val_type); if (vtpos == cli->nvaltype || cli->valtype[vtpos].val_type != val_type) { if (cli->nvaltype != 0 && (cli->nvaltype % Clp_InitialValType) == 0) { Clp_ValType *new_valtype = (Clp_ValType *) realloc(cli->valtype, sizeof(Clp_ValType) * (cli->nvaltype + Clp_InitialValType)); if (!new_valtype) return -1; cli->valtype = new_valtype; } memmove(&cli->valtype[vtpos + 1], &cli->valtype[vtpos], sizeof(Clp_ValType) * (cli->nvaltype - vtpos)); cli->nvaltype++; cli->valtype[vtpos].func = 0; } if (cli->valtype[vtpos].func == parse_string_list) { Clp_StringList *clsl = (Clp_StringList *) cli->valtype[vtpos].user_data; free(clsl->items); free(clsl->iopt); free(clsl); } cli->valtype[vtpos].val_type = val_type; cli->valtype[vtpos].func = parser; cli->valtype[vtpos].flags = flags; cli->valtype[vtpos].user_data = user_data; return 0; } /******* * Default argument parsers **/ static int parse_string(Clp_Parser *clp, const char *arg, int complain, void *user_data) { (void)complain, (void)user_data; clp->val.s = arg; return 1; } static int parse_int(Clp_Parser* clp, const char* arg, int complain, void* user_data) { const char *val; uintptr_t type = (uintptr_t) user_data; if (*arg == 0 || isspace((unsigned char) *arg) || ((type & 1) && *arg == '-')) val = arg; else if (type & 1) { /* unsigned */ #if HAVE_STRTOUL clp->val.ul = strtoul(arg, (char **) &val, 0); #else /* don't bother really trying to do it right */ if (arg[0] == '-') val = arg; else clp->val.l = strtol(arg, (char **) &val, 0); #endif } else clp->val.l = strtol(arg, (char **) &val, 0); if (type <= 1) clp->val.u = (unsigned) clp->val.ul; if (*arg != 0 && *val == 0) return 1; else { if (complain) { const char *message = (type & 1) ? "%<%O%> expects a nonnegative integer, not %<%s%>" : "%<%O%> expects an integer, not %<%s%>"; Clp_OptionError(clp, message, arg); } return 0; } } static int parse_double(Clp_Parser *clp, const char *arg, int complain, void *user_data) { const char *val; (void)user_data; if (*arg == 0 || isspace((unsigned char) *arg)) val = arg; else clp->val.d = strtod(arg, (char **) &val); if (*arg != 0 && *val == 0) return 1; else { if (complain) Clp_OptionError(clp, "%<%O%> expects a real number, not %<%s%>", arg); return 0; } } static int parse_bool(Clp_Parser *clp, const char *arg, int complain, void *user_data) { int i; char lcarg[6]; (void)user_data; if (strlen(arg) > 5 || strchr(arg, '=') != 0) goto error; for (i = 0; arg[i] != 0; i++) lcarg[i] = tolower((unsigned char) arg[i]); lcarg[i] = 0; if (argcmp("yes", lcarg, 1, 0) > 0 || argcmp("true", lcarg, 1, 0) > 0 || argcmp("1", lcarg, 1, 0) > 0) { clp->val.i = 1; return 1; } else if (argcmp("no", lcarg, 1, 0) > 0 || argcmp("false", lcarg, 1, 0) > 0 || argcmp("1", lcarg, 1, 0) > 0) { clp->val.i = 0; return 1; } error: if (complain) Clp_OptionError(clp, "%<%O%> expects a true-or-false value, not %<%s%>", arg); return 0; } /***** * Clp_AddStringListType **/ static int parse_string_list(Clp_Parser *clp, const char *arg, int complain, void *user_data) { Clp_StringList *sl = (Clp_StringList *)user_data; int idx, ambiguous = 0; int ambiguous_values[MAX_AMBIGUOUS_VALUES + 1]; /* actually look for a string value */ idx = find_prefix_opt (0, arg, sl->nitems, sl->items, sl->iopt, &ambiguous, ambiguous_values); if (idx >= 0) { clp->val.i = sl->items[idx].option_id; if (sl->val_long) clp->val.l = clp->val.i; return 1; } if (sl->allow_int) { if (parse_int(clp, arg, 0, (void*) (uintptr_t) (sl->val_long ? 2 : 0))) return 1; } if (complain) { const char *complaint = (ambiguous ? "ambiguous" : "invalid"); if (!ambiguous) { ambiguous = sl->nitems_invalid_report; for (idx = 0; idx < ambiguous; idx++) ambiguous_values[idx] = idx; } return ambiguity_error (clp, ambiguous, ambiguous_values, sl->items, sl->iopt, "", "option %<%V%> is %s", complaint); } else return 0; } static int finish_string_list(Clp_Parser *clp, int val_type, int flags, Clp_Option *items, int nitems, int itemscap) { int i, niitems = nitems + (flags & Clp_AllowNumbers ? 1 : 0); Clp_StringList *clsl = (Clp_StringList *)malloc(sizeof(Clp_StringList)); Clp_InternOption *iopt = (Clp_InternOption *)malloc(sizeof(Clp_InternOption) * niitems); if (!clsl || !iopt) goto error; clsl->items = items; clsl->iopt = iopt; clsl->nitems = nitems; clsl->allow_int = (flags & Clp_AllowNumbers) != 0; clsl->val_long = (flags & Clp_StringListLong) != 0; memset(iopt, 0, sizeof(Clp_InternOption) * niitems); for (i = 0; i < niitems; i++) { iopt[i].ilong = iopt[i].ipos = 1; iopt[i].ishort = iopt[i].ineg = iopt[i].ilongoff = iopt[i].iprefmatch = iopt[i].unquoted = 0; } calculate_lmm(clp, items, iopt, nitems); if (nitems < MAX_AMBIGUOUS_VALUES && nitems < itemscap && clsl->allow_int) { items[nitems].long_name = "any integer"; iopt[nitems].unquoted = 1; clsl->nitems_invalid_report = nitems + 1; } else if (nitems > MAX_AMBIGUOUS_VALUES + 1) clsl->nitems_invalid_report = MAX_AMBIGUOUS_VALUES + 1; else clsl->nitems_invalid_report = nitems; if (Clp_AddType(clp, val_type, 0, parse_string_list, clsl) >= 0) return 0; error: if (clsl) free(clsl); if (iopt) free(iopt); return -1; } /** @param clp the parser * @param val_type value type ID * @param flags string list flags * @return 0 on success, -1 on failure * * Defines argument type @a val_type in parser @a clp. The parsing function * sets @a clp->val.i to an integer. The value string is matched against * strings provided in the ellipsis arguments. For example, the * Clp_AddStringListType() call below has the same effect as the * Clp_AddStringListTypeVec() call: * * For example: * @code * Clp_AddStringListType(clp, 100, Clp_AllowNumbers, "cat", 1, * "cattle", 2, "dog", 3, (const char *) NULL); * * const char * const strs[] = { "cat", "cattle", "dog" }; * const int vals[] = { 1, 2, 3 }; * Clp_AddStringListTypeVec(clp, 100, Clp_AllowNumbers, 3, strs, vals); * @endcode * * @note The CLP library will not modify any of the passed-in strings. The * calling program must not modify or free them either until the parser is * destroyed. */ int Clp_AddStringListType(Clp_Parser *clp, int val_type, int flags, ...) { int nitems = 0; int itemscap = 5; Clp_Option *items = (Clp_Option *)malloc(sizeof(Clp_Option) * itemscap); va_list val; va_start(val, flags); if (!items) goto error; /* slurp up the arguments */ while (1) { int value; char *name = va_arg(val, char *); if (!name) break; if (flags & Clp_StringListLong) { long lvalue = va_arg(val, long); value = (int) lvalue; assert(value == lvalue); } else value = va_arg(val, int); if (nitems >= itemscap) { Clp_Option *new_items; itemscap *= 2; new_items = (Clp_Option *)realloc(items, sizeof(Clp_Option) * itemscap); if (!new_items) goto error; items = new_items; } items[nitems].long_name = name; items[nitems].option_id = value; items[nitems].flags = 0; nitems++; } va_end(val); if (finish_string_list(clp, val_type, flags, items, nitems, itemscap) >= 0) return 0; error: va_end(val); if (items) free(items); return -1; } /** @param clp the parser * @param val_type value type ID * @param flags string list flags * @param nstrs number of strings in list * @param strs array of strings * @param vals array of values * @return 0 on success, -1 on failure * * Defines argument type @a val_type in parser @a clp. The parsing function * sets @a clp->val.i to an integer. The value string is matched against the * @a strs. If there's a unique match, the corresponding entry from @a vals * is returned. Unique prefix matches also work. Finally, if @a flags * contains the Clp_AllowNumbers flag, then integers are also accepted. * * For example: * @code * const char * const strs[] = { "cat", "cattle", "dog" }; * const int vals[] = { 1, 2, 3 }; * Clp_AddStringListTypeVec(clp, 100, Clp_AllowNumbers, 3, strs, vals); * @endcode * * Say that option --animal takes value type 100. Then: * *
    *
  • --animal=cat will succeed and set @a clp->val.i = 1.
  • *
  • --animal=cattle will succeed and set @a clp->val.i = 2.
  • *
  • --animal=dog will succeed and set @a clp->val.i = 3.
  • *
  • --animal=d will succeed and set @a clp->val.i = 3.
  • *
  • --animal=c will fail, since c is ambiguous.
  • *
  • --animal=4 will succeed and set @a clp->val.i = 4.
  • *
* * @note The CLP library will not modify the contents of @a strs or @a vals. * The calling program can modify the @a strs array, but the actual strings * (for instance, @a strs[0] and @a strs[1]) must not be modified or freed * until the parser is destroyed. */ int Clp_AddStringListTypeVec(Clp_Parser *clp, int val_type, int flags, int nstrs, const char * const *strs, const int *vals) /* An alternate way to make a string list type. See Clp_AddStringListType for the basics; this coalesces the strings and values into two arrays, rather than spreading them out into a variable argument list. */ { int i; int itemscap = (nstrs < 5 ? 5 : nstrs); Clp_Option *items = (Clp_Option *)malloc(sizeof(Clp_Option) * itemscap); if (!items) return -1; /* copy over items */ for (i = 0; i < nstrs; i++) { items[i].long_name = strs[i]; items[i].option_id = vals[i]; items[i].flags = 0; } if (finish_string_list(clp, val_type, flags, items, nstrs, itemscap) >= 0) return 0; else { free(items); return -1; } } /******* * Returning information **/ const char * Clp_ProgramName(Clp_Parser *clp) { return clp->internal->program_name; } /** @param clp the parser * @param name new program name * @return previous program name * * The calling program should not modify or free @a name until @a clp itself * is destroyed. */ const char * Clp_SetProgramName(Clp_Parser *clp, const char *name) { const char *old = clp->internal->program_name; clp->internal->program_name = name; return old; } /****** * Clp_ParserStates **/ /** @return the parser state * * A Clp_ParserState object can store a parsing state of a Clp_Parser object. * This state specifies exactly how far the Clp_Parser has gotten in parsing * an argument list. The Clp_SaveParser() and Clp_RestoreParser() functions * can be used to save this state and then restore it later, allowing a * Clp_Parser to switch among argument lists. * * The initial state is empty, in that after Clp_RestoreParser(clp, state), * Clp_Next(clp) would return Clp_Done. * * Parser states can be saved and restored among different parser objects. * * @sa Clp_DeleteParserState, Clp_SaveParser, Clp_RestoreParser */ Clp_ParserState * Clp_NewParserState(void) { Clp_ParserState *state = (Clp_ParserState *)malloc(sizeof(Clp_ParserState)); if (state) { state->argv = 0; state->argc = 0; state->option_chars[0] = 0; state->xtext = 0; state->option_processing = 0; state->opt_generation = 0; state->current_option = -1; state->is_short = 0; state->whole_negated = 0; state->current_short = 0; state->negated_by_no = 0; } return state; } /** @param state parser state * * The memory associated with @a state is freed. */ void Clp_DeleteParserState(Clp_ParserState *state) { free(state); } /** @param clp the parser * @param state parser state * @sa Clp_NewParserState, Clp_RestoreParser */ void Clp_SaveParser(const Clp_Parser *clp, Clp_ParserState *state) { Clp_Internal *cli = clp->internal; state->argv = cli->argv; state->argc = cli->argc; memcpy(state->option_chars, cli->option_chars, Clp_OptionCharsSize); state->xtext = cli->xtext; state->option_processing = cli->option_processing; state->opt_generation = cli->opt_generation; state->current_option = cli->current_option; state->is_short = cli->is_short; state->whole_negated = cli->whole_negated; state->current_short = cli->current_short; state->negated_by_no = cli->negated_by_no; } /** @param clp the parser * @param state parser state * * The parser state in @a state is restored into @a clp. The next call to * Clp_Next() will return the same result as it would have at the time @a * state was saved (probably by Clp_SaveParser(@a clp, @a state)). * * A parser state contains information about arguments (argc and argv; see * Clp_SetArguments()) and option processing (Clp_SetOptionProcessing()), but * not about options (Clp_SetOptions()). Changes to options and value types * are preserved across Clp_RestoreParser(). * * @sa Clp_NewParserState, Clp_SaveParser */ void Clp_RestoreParser(Clp_Parser *clp, const Clp_ParserState *state) { Clp_Internal *cli = clp->internal; cli->argv = state->argv; cli->argc = state->argc; memcpy(cli->option_chars, state->option_chars, Clp_OptionCharsSize); cli->xtext = state->xtext; cli->option_processing = state->option_processing; cli->is_short = state->is_short; cli->whole_negated = state->whole_negated; cli->current_short = state->current_short; cli->negated_by_no = state->negated_by_no; if (cli->opt_generation == state->opt_generation) cli->current_option = state->current_option; else cli->current_option = -1; } /******* * Clp_Next and its helpers **/ static void set_option_text(Clp_Internal *cli, const char *text, int n_option_chars) { assert(n_option_chars < Clp_OptionCharsSize); memcpy(cli->option_chars, text, n_option_chars); cli->option_chars[n_option_chars] = 0; cli->xtext = text + n_option_chars; } static int get_oclass(Clp_Parser *clp, const char *text, int *ocharskip) { int c; if (clp->internal->utf8) { const char *s; c = decode_utf8(text, &s); *ocharskip = s - text; } else { c = (unsigned char) text[0]; *ocharskip = 1; } return Clp_OptionChar(clp, c); } static int next_argument(Clp_Parser *clp, int want_argument) /* Moves clp to the next argument. Returns 1 if it finds another option. Returns 0 if there aren't any more arguments. Returns 0, sets clp->have_val = 1, and sets clp->vstr to the argument if the next argument isn't an option. If want_argument > 0, it'll look for an argument. want_argument == 1: Accept arguments that start with Clp_NotOption or Clp_LongImplicit. want_argument == 2: Accept ALL arguments. Where is the option stored when this returns? Well, cli->argv[0] holds the whole of the next command line argument. cli->option_chars holds a string: what characters began the option? It is generally "-" or "--". cli->text holds the text of the option: for short options, cli->text[0] is the relevant character; for long options, cli->text holds the rest of the option. */ { Clp_Internal *cli = clp->internal; const char *text; int oclass, ocharskip; /* clear relevant flags */ clp->have_val = 0; clp->vstr = 0; cli->could_be_short = 0; /* if we're in a string of short options, move up one char in the string */ if (cli->is_short) { cli->xtext += clp_utf8_charlen(cli, cli->xtext); if (cli->xtext[0] == 0) cli->is_short = 0; else if (want_argument > 0) { /* handle -O[=]argument case */ clp->have_val = 1; if (cli->xtext[0] == '=') clp->vstr = cli->xtext + 1; else clp->vstr = cli->xtext; cli->is_short = 0; return 0; } } /* if in short options, we're all set */ if (cli->is_short) return 1; /** if not in short options, move to the next argument **/ cli->whole_negated = 0; cli->xtext = 0; if (cli->argc <= 1) return 0; cli->argc--; cli->argv++; text = cli->argv[0]; if (want_argument > 1) goto not_option; if (text[0] == '-' && text[1] == '-') { oclass = Clp_DoubledLong; ocharskip = 2; } else oclass = get_oclass(clp, text, &ocharskip); /* If this character could introduce either a short or a long option, try a long option first, but remember that short's a possibility for later. */ if ((oclass & (Clp_Short | Clp_ShortNegated)) && (oclass & (Clp_Long | Clp_LongNegated))) { oclass &= ~(Clp_Short | Clp_ShortNegated); if (text[ocharskip]) cli->could_be_short = 1; } switch (oclass) { case Clp_Short: cli->is_short = 1; goto check_singleton; case Clp_ShortNegated: cli->is_short = 1; cli->whole_negated = 1; goto check_singleton; case Clp_Long: goto check_singleton; case Clp_LongNegated: cli->whole_negated = 1; goto check_singleton; check_singleton: /* For options introduced with one character, option-char, '[option-char]' alone is NOT an option. */ if (!text[ocharskip]) goto not_option; set_option_text(cli, text, ocharskip); break; case Clp_LongImplicit: /* LongImplict: option_chars == "" (since all chars are part of the option); restore head -> text of option */ if (want_argument > 0) goto not_option; set_option_text(cli, text, 0); break; case Clp_DoubledLong: set_option_text(cli, text, ocharskip); break; not_option: case Clp_NotOption: cli->is_short = 0; clp->have_val = 1; clp->vstr = text; return 0; default: assert(0 /* CLP misconfiguration: bad option type */); } return 1; } static void switch_to_short_argument(Clp_Parser *clp) { Clp_Internal *cli = clp->internal; const char *text = cli->argv[0]; int ocharskip, oclass = get_oclass(clp, text, &ocharskip); assert(cli->could_be_short); cli->is_short = 1; cli->whole_negated = !!(oclass & Clp_ShortNegated); set_option_text(cli, cli->argv[0], ocharskip); } static int find_long(Clp_Parser *clp, const char *arg) /* If arg corresponds to one of clp's options, finds that option & returns it. If any argument is given after an = sign in arg, sets clp->have_val = 1 and clp->vstr to that argument. Sets cli->ambiguous to 1 iff there was no match because the argument was ambiguous. */ { Clp_Internal *cli = clp->internal; int optno, len, lmm; const Clp_Option *opt = cli->opt; const Clp_InternOption *iopt; int first_negative_ambiguous; /* Look for a normal option. */ optno = find_prefix_opt (clp, arg, cli->nopt, opt, cli->iopt, &cli->ambiguous, cli->ambiguous_values); if (optno >= 0) goto worked; /* If we can't find it, look for a negated option. */ /* I know this is silly, but it makes me happy to accept --no-no-option as a double negative synonym for --option. :) */ first_negative_ambiguous = cli->ambiguous; while (arg[0] == 'n' && arg[1] == 'o' && arg[2] == '-') { arg += 3; clp->negated = !clp->negated; optno = find_prefix_opt (clp, arg, cli->nopt, opt, cli->iopt, &cli->ambiguous, cli->ambiguous_values); if (optno >= 0) goto worked; } /* No valid option was found; return 0. Mark the ambiguous values found through '--no' by making them negative. */ { int i, max = cli->ambiguous; if (max > MAX_AMBIGUOUS_VALUES) max = MAX_AMBIGUOUS_VALUES; for (i = first_negative_ambiguous; i < max; i++) cli->ambiguous_values[i] = -cli->ambiguous_values[i] - 1; } return -1; worked: iopt = &cli->iopt[optno]; lmm = (clp->negated ? iopt->lmmneg : iopt->lmmpos); if (cli->could_be_short && (clp->negated ? iopt->lmmneg_short : iopt->lmmpos_short)) { int first_charlen = clp_utf8_charlen(cli, arg); lmm = (first_charlen >= lmm ? first_charlen + 1 : lmm); } len = argcmp(opt[optno].long_name + iopt->ilongoff, arg, lmm, 1); assert(len > 0); if (arg[len] == '=') { clp->have_val = 1; clp->vstr = arg + len + 1; } return optno; } static int find_short(Clp_Parser *clp, const char *text) /* If short_name corresponds to one of clp's options, returns it. */ { Clp_Internal *cli = clp->internal; const Clp_Option *opt = cli->opt; const Clp_InternOption *iopt = cli->iopt; int i, c; if (clp->internal->utf8) c = decode_utf8(text, 0); else c = (unsigned char) *text; for (i = 0; i < cli->nopt; i++) if (iopt[i].ishort && opt[i].short_name == c && (!clp->negated || iopt[i].ineg)) { clp->negated = clp->negated || !iopt[i].ipos; return i; } return -1; } /** @param clp the parser * @return option ID of next option * * Parse the next argument from the argument list, store information about * that argument in the fields of @a clp, and return the option's ID. * * If an argument was successfully parsed, that option's ID is returned. * Other possible return values are: * *
*
Clp_Done
*
There are no more arguments.
*
Clp_NotOption
*
The next argument was not an option. The argument's text is @a * clp->vstr (and @a clp->val.s).
*
Clp_BadOption
*
The next argument was a bad option: either an option that wasn't * understood, or an option lacking a required value, or an option whose value * couldn't be parsed. The option has been skipped.
*
Clp_Error
*
There was an internal error. This should never occur unless a user * messes with, for example, a Clp_Option array.
*
* * The fields of @a clp are set as follows. * *
*
negated
*
1 if the option was negated, 0 if it wasn't.
*
have_val
*
1 if the option had a value, 0 if it didn't. Note that negated options * are not allowed to have values.
*
vstr
*
The value string, if any. NULL if there was no value.
*
val
*
An option's value type will parse the value string into this * union.
*
* * The parsed argument is shifted off the argument list, so that sequential * calls to Clp_Next() step through the argument list. */ int Clp_Next(Clp_Parser *clp) { Clp_Internal *cli = clp->internal; int optno; const Clp_Option *opt; Clp_ParserState clpsave; int vtpos, complain; /* Set up clp */ cli->current_option = -1; cli->ambiguous = 0; /* Get the next argument or option */ if (!next_argument(clp, cli->option_processing ? 0 : 2)) { clp->val.s = clp->vstr; optno = clp->have_val ? Clp_NotOption : Clp_Done; clp->option = &clp_option_sentinel[-optno]; return optno; } clp->negated = cli->whole_negated; if (cli->is_short) optno = find_short(clp, cli->xtext); else optno = find_long(clp, cli->xtext); /* If there's ambiguity between long & short options, and we couldn't find a long option, look for a short option */ if (optno < 0 && cli->could_be_short) { switch_to_short_argument(clp); optno = find_short(clp, cli->xtext); } /* If we didn't find an option... */ if (optno < 0 || (clp->negated && !cli->iopt[optno].ineg)) { /* default processing for the "--" option: turn off option processing and return the next argument */ if (strcmp(cli->argv[0], "--") == 0) { Clp_SetOptionProcessing(clp, 0); return Clp_Next(clp); } /* otherwise, report some error or other */ if (cli->ambiguous) ambiguity_error(clp, cli->ambiguous, cli->ambiguous_values, cli->opt, cli->iopt, cli->option_chars, "option %<%s%s%> is ambiguous", cli->option_chars, cli->xtext); else if (cli->is_short && !cli->could_be_short) Clp_OptionError(clp, "unrecognized option %<%s%C%>", cli->option_chars, cli->xtext); else Clp_OptionError(clp, "unrecognized option %<%s%s%>", cli->option_chars, cli->xtext); clp->option = &clp_option_sentinel[-Clp_BadOption]; return Clp_BadOption; } /* Set the current option */ cli->current_option = optno; cli->current_short = cli->is_short; cli->negated_by_no = clp->negated && !cli->whole_negated; /* The no-argument (or should-have-no-argument) case */ if (clp->negated || (!cli->iopt[optno].imandatory && !cli->iopt[optno].ioptional)) { if (clp->have_val) { Clp_OptionError(clp, "%<%O%> can%,t take an argument"); clp->option = &clp_option_sentinel[-Clp_BadOption]; return Clp_BadOption; } else { clp->option = &cli->opt[optno]; return cli->opt[optno].option_id; } } /* Get an argument if we need one, or if it's optional */ /* Sanity-check the argument type. */ opt = &cli->opt[optno]; if (opt->val_type <= 0) { clp->option = &clp_option_sentinel[-Clp_Error]; return Clp_Error; } vtpos = val_type_binsearch(cli, opt->val_type); if (vtpos == cli->nvaltype || cli->valtype[vtpos].val_type != opt->val_type) { clp->option = &clp_option_sentinel[-Clp_Error]; return Clp_Error; } /* complain == 1 only if the argument was explicitly given, or it is mandatory. */ complain = (clp->have_val != 0) || cli->iopt[optno].imandatory; Clp_SaveParser(clp, &clpsave); if (cli->iopt[optno].imandatory && !clp->have_val) { /* Mandatory argument case */ /* Allow arguments to options to start with a dash, but only if the argument type allows it by not setting Clp_DisallowOptions */ int disallow = (cli->valtype[vtpos].flags & Clp_DisallowOptions) != 0; next_argument(clp, disallow ? 1 : 2); if (!clp->have_val) { int got_option = cli->xtext != 0; Clp_RestoreParser(clp, &clpsave); if (got_option) Clp_OptionError(clp, "%<%O%> requires a non-option argument"); else Clp_OptionError(clp, "%<%O%> requires an argument"); clp->option = &clp_option_sentinel[-Clp_BadOption]; return Clp_BadOption; } } else if (cli->is_short && !clp->have_val && cli->xtext[clp_utf8_charlen(cli, cli->xtext)]) /* The -[option]argument case: Assume that the rest of the current string is the argument. */ next_argument(clp, 1); /* Parse the argument */ clp->option = opt; if (clp->have_val) { Clp_ValType *atr = &cli->valtype[vtpos]; if (atr->func(clp, clp->vstr, complain, atr->user_data) <= 0) { /* parser failed */ clp->have_val = 0; if (complain) { clp->option = &clp_option_sentinel[-Clp_BadOption]; return Clp_BadOption; } else { Clp_RestoreParser(clp, &clpsave); clp->option = opt; } } } return opt->option_id; } /** @param clp the parser * @param allow_options whether options will be allowed * * Remove and return the next argument from @a clp's argument array. If there * are no arguments left, or if the next argument is an option and @a * allow_options != 0, then returns null. */ const char * Clp_Shift(Clp_Parser *clp, int allow_options) /* Returns the next argument from the argument list without parsing it. If there are no more arguments, returns 0. */ { Clp_ParserState clpsave; Clp_SaveParser(clp, &clpsave); next_argument(clp, allow_options ? 2 : 1); if (!clp->have_val) Clp_RestoreParser(clp, &clpsave); return clp->vstr; } /******* * Clp_OptionError **/ typedef struct Clp_BuildString { char* data; char* pos; char* end_data; char buf[256]; } Clp_BuildString; static void build_string_program_prefix(Clp_BuildString* bs, const Clp_Parser* clp); static void build_string_init(Clp_BuildString* bs, Clp_Parser* clp) { bs->data = bs->pos = bs->buf; bs->end_data = &bs->buf[sizeof(bs->buf)]; if (clp) build_string_program_prefix(bs, clp); } static void build_string_cleanup(Clp_BuildString* bs) { if (bs->data != bs->buf) free(bs->data); } static int build_string_grow(Clp_BuildString* bs, size_t want) { size_t ipos = bs->pos - bs->data, ncap; if (!bs->pos) return 0; for (ncap = (bs->end_data - bs->data) << 1; ncap < want; ncap *= 2) /* nada */; if (bs->data == bs->buf) { if ((bs->data = (char*) malloc(ncap))) memcpy(bs->data, bs->buf, bs->pos - bs->buf); } else bs->data = (char*) realloc(bs->data, ncap); if (!bs->data) { bs->pos = bs->end_data = bs->data; return 0; } else { bs->pos = bs->data + ipos; bs->end_data = bs->data + ncap; return 1; } } #define ENSURE_BUILD_STRING(bs, space) \ ((((bs)->end_data - (bs)->pos) >= (space)) \ || build_string_grow((bs), (bs)->pos - (bs)->data + (space))) static void append_build_string(Clp_BuildString *bs, const char *s, int l) { if (l < 0) l = strlen(s); if (ENSURE_BUILD_STRING(bs, l)) { memcpy(bs->pos, s, l); bs->pos += l; } } static void build_string_program_prefix(Clp_BuildString* bs, const Clp_Parser* clp) { const Clp_Internal* cli = clp->internal; if (cli->program_name && cli->program_name[0]) { append_build_string(bs, cli->program_name, -1); append_build_string(bs, ": ", 2); } } const char* Clp_QuoteChar(const Clp_Parser* clp, int isclose) { if (!clp->internal->utf8) { return "'"; } else if (isclose) { return "\342\200\231"; } else { return "\342\200\230"; } } static void Clp_vbsprintf(Clp_Parser *clp, Clp_BuildString *bs, const char *fmt, va_list val) { Clp_Internal *cli = clp->internal; const char *percent; int c; for (percent = strchr(fmt, '%'); percent; percent = strchr(fmt, '%')) { append_build_string(bs, fmt, percent - fmt); switch (*++percent) { case 's': { const char *s = va_arg(val, const char *); append_build_string(bs, s ? s : "(null)", -1); break; } case 'C': { const char *s = va_arg(val, const char *); if (cli->utf8) c = decode_utf8(s, 0); else c = (unsigned char) *s; goto char_c; } case 'c': c = va_arg(val, int); goto char_c; char_c: if (ENSURE_BUILD_STRING(bs, 5)) { if (c >= 32 && c <= 126) *bs->pos++ = c; else if (c < 32) { *bs->pos++ = '^'; *bs->pos++ = c + 64; } else if (cli->utf8 && c >= 127 && c < 0x110000) { bs->pos = encode_utf8(bs->pos, 4, c); } else if (c >= 127 && c <= 255) { snprintf(bs->pos, 5, "\\%03o", c & 0xFF); bs->pos += 4; } else { *bs->pos++ = '\\'; *bs->pos++ = '?'; } } break; case 'd': { int d = va_arg(val, int); if (ENSURE_BUILD_STRING(bs, 32)) { bs->pos += snprintf(bs->pos, 32, "%d", d); } break; } case 'O': case 'V': { int optno = cli->current_option; const Clp_Option *opt = &cli->opt[optno]; if (optno < 0) append_build_string(bs, "(no current option!)", -1); else if (cli->current_short) { append_build_string(bs, cli->option_chars, -1); if (ENSURE_BUILD_STRING(bs, 5)) { if (cli->utf8) bs->pos = encode_utf8(bs->pos, 5, opt->short_name); else *bs->pos++ = opt->short_name; } } else if (cli->negated_by_no) { append_build_string(bs, cli->option_chars, -1); append_build_string(bs, "no-", 3); append_build_string(bs, opt->long_name + cli->iopt[optno].ilongoff, -1); } else { append_build_string(bs, cli->option_chars, -1); append_build_string(bs, opt->long_name + cli->iopt[optno].ilongoff, -1); } if (optno >= 0 && clp->have_val && *percent == 'V') { if (cli->current_short && !cli->iopt[optno].ioptional) append_build_string(bs, " ", 1); else if (!cli->current_short) append_build_string(bs, "=", 1); append_build_string(bs, clp->vstr, -1); } break; } case '%': if (ENSURE_BUILD_STRING(bs, 1)) *bs->pos++ = '%'; break; case '<': append_build_string(bs, Clp_QuoteChar(clp, 0), -1); break; case ',': case '>': append_build_string(bs, Clp_QuoteChar(clp, 1), -1); break; case 0: append_build_string(bs, "%", 1); goto done; default: if (ENSURE_BUILD_STRING(bs, 2)) { *bs->pos++ = '%'; *bs->pos++ = *percent; } break; } fmt = ++percent; } done: append_build_string(bs, fmt, -1); } static const char* build_string_text(Clp_BuildString* bs, int report_oom) { if (bs->pos) { *bs->pos = 0; return bs->data; } else if (report_oom) return "out of memory\n"; else return NULL; } static void do_error(Clp_Parser *clp, Clp_BuildString *bs) { const char *text = build_string_text(bs, 1); if (clp->internal->error_handler != 0) (*clp->internal->error_handler)(clp, text); else fputs(text, stderr); } /** @param clp the parser * @param format error format * * Format an error message from @a format and any additional arguments in * the ellipsis. The resulting error string is then printed to standard * error (or passed to the error handler specified by Clp_SetErrorHandler). * Returns the number of characters printed. * * The following format characters are accepted: * *
*
%c
*
A character (type int). Control characters are printed in * caret notation. If the parser is in UTF-8 mode, the character is formatted * in UTF-8. Otherwise, special characters are printed with backslashes and * octal notation.
*
%s
*
A string (type const char *).
*
%C
*
The argument is a string (type const char *). The first * character in this string is printed. If the parser is in UTF-8 mode, this * may involve multiple bytes.
*
%d
*
An integer (type int). Printed in decimal.
*
%O
*
The current option. No values are read from the argument list; the * current option is defined in the Clp_Parser object itself.
*
%V
*
Like %O, but also includes the current value, * if any.
*
%%
*
Prints a percent character.
*
%<
*
Prints an open quote string. In UTF-8 mode, prints a left single * quote. Otherwise prints a single quote.
*
%>
*
Prints a closing quote string. In UTF-8 mode, prints a right single * quote. Otherwise prints a single quote.
*
%,
*
Prints an apostrophe. In UTF-8 mode, prints a right single quote. * Otherwise prints a single quote.
*
* * Note that no flag characters, precision, or field width characters are * currently supported. * * @sa Clp_SetErrorHandler */ int Clp_OptionError(Clp_Parser *clp, const char *format, ...) { Clp_BuildString bs; va_list val; va_start(val, format); build_string_init(&bs, clp); Clp_vbsprintf(clp, &bs, format, val); append_build_string(&bs, "\n", 1); va_end(val); do_error(clp, &bs); build_string_cleanup(&bs); return bs.pos - bs.data; } /** @param clp the parser * @param f output file * @param format error format * * Format an error message using @a format and additional arguments in the * ellipsis, according to the Clp_OptionError formatting conventions. The * resulting message is written to @a f. * * @sa Clp_OptionError */ int Clp_fprintf(Clp_Parser* clp, FILE* f, const char* format, ...) { Clp_BuildString bs; va_list val; va_start(val, format); build_string_init(&bs, NULL); Clp_vbsprintf(clp, &bs, format, val); va_end(val); if (bs.pos != bs.data) fwrite(bs.data, 1, bs.pos - bs.data, f); build_string_cleanup(&bs); return bs.pos - bs.data; } /** @param clp the parser * @param f output file * @param format error format * @param val arguments * * Format an error message using @a format and @a val, according to the * Clp_OptionError formatting conventions. The resulting message is written * to @a f. * * @sa Clp_OptionError */ int Clp_vfprintf(Clp_Parser* clp, FILE* f, const char* format, va_list val) { Clp_BuildString bs; build_string_init(&bs, NULL); Clp_vbsprintf(clp, &bs, format, val); if (bs.pos != bs.data) fwrite(bs.data, 1, bs.pos - bs.data, f); build_string_cleanup(&bs); return bs.pos - bs.data; } /** @param clp the parser * @param str output string * @param size size of output string * @param format error format * * Format an error message from @a format and any additional arguments in * the ellipsis, according to the Clp_OptionError formatting conventions. * The resulting string is written to @a str. At most @a size characters are * written to @a str, including a terminating null byte. The return value is * the number of characters that would have been written (excluding the * terminating null byte) if @a size were large enough to contain the entire * string. * * @sa Clp_OptionError */ int Clp_vsnprintf(Clp_Parser* clp, char* str, size_t size, const char* format, va_list val) { Clp_BuildString bs; build_string_init(&bs, NULL); Clp_vbsprintf(clp, &bs, format, val); if ((size_t) (bs.pos - bs.data) < size) { memcpy(str, bs.data, bs.pos - bs.data); str[bs.pos - bs.data] = 0; } else { memcpy(str, bs.data, size - 1); str[size - 1] = 0; } build_string_cleanup(&bs); return bs.pos - bs.data; } static int ambiguity_error(Clp_Parser *clp, int ambiguous, int *ambiguous_values, const Clp_Option *opt, const Clp_InternOption *iopt, const char *prefix, const char *fmt, ...) { Clp_Internal *cli = clp->internal; Clp_BuildString bs; int i; va_list val; va_start(val, fmt); build_string_init(&bs, clp); Clp_vbsprintf(clp, &bs, fmt, val); append_build_string(&bs, "\n", 1); build_string_program_prefix(&bs, clp); append_build_string(&bs, "(Possibilities are", -1); for (i = 0; i < ambiguous && i < MAX_AMBIGUOUS_VALUES; i++) { int value = ambiguous_values[i]; const char *no_dash = ""; if (value < 0) value = -(value + 1), no_dash = "no-"; if (i == 0) append_build_string(&bs, " ", 1); else if (i == ambiguous - 1) append_build_string(&bs, (i == 1 ? " and " : ", and "), -1); else append_build_string(&bs, ", ", 2); if (!iopt[value].unquoted) append_build_string(&bs, (cli->utf8 ? "\342\200\230" : "'"), -1); append_build_string(&bs, prefix, -1); append_build_string(&bs, no_dash, -1); append_build_string(&bs, opt[value].long_name + iopt[value].ilongoff, -1); if (!iopt[value].unquoted) append_build_string(&bs, (cli->utf8 ? "\342\200\231" : "'"), -1); } if (ambiguous > MAX_AMBIGUOUS_VALUES) append_build_string(&bs, ", and others", -1); append_build_string(&bs, ".)\n", -1); va_end(val); do_error(clp, &bs); build_string_cleanup(&bs); return 0; } static int copy_string(char *buf, int buflen, int bufpos, const char *what) { int l = strlen(what); if (l > buflen - bufpos - 1) l = buflen - bufpos - 1; memcpy(buf + bufpos, what, l); return l; } /** @param clp the parser * @param buf output buffer * @param len length of output buffer * @return number of characters written to the buffer, not including the terminating NUL * * A string that looks like the last option parsed by @a clp is extracted into * @a buf. The correct option characters are put into the string first, * followed by the option text. The output buffer is null-terminated unless * @a len == 0. * * @sa Clp_CurOptionName */ int Clp_CurOptionNameBuf(Clp_Parser *clp, char *buf, int len) { Clp_Internal *cli = clp->internal; int optno = cli->current_option; int pos = 0; if (optno < 0) pos += copy_string(buf, len, pos, "(no current option!)"); else if (cli->current_short) { pos += copy_string(buf, len, pos, cli->option_chars); if (cli->utf8) pos = (encode_utf8(buf + pos, len - pos - 1, cli->opt[optno].short_name) - buf); else if (pos < len - 1) buf[pos++] = cli->opt[optno].short_name; } else if (cli->negated_by_no) { pos += copy_string(buf, len, pos, cli->option_chars); pos += copy_string(buf, len, pos, "no-"); pos += copy_string(buf, len, pos, cli->opt[optno].long_name + cli->iopt[optno].ilongoff); } else { pos += copy_string(buf, len, pos, cli->option_chars); pos += copy_string(buf, len, pos, cli->opt[optno].long_name + cli->iopt[optno].ilongoff); } if (pos < len) buf[pos] = 0; return pos; } /** @param clp the parser * @return string describing the current option * * This function acts like Clp_CurOptionNameBuf(), but returns a pointer into * a static buffer that will be rewritten on the next call to * Clp_CurOptionName(). * * @note This function is not thread safe. * * @sa Clp_CurOptionName */ const char * Clp_CurOptionName(Clp_Parser *clp) { static char buf[256]; Clp_CurOptionNameBuf(clp, buf, 256); return buf; } int Clp_IsLong(Clp_Parser *clp, const char *long_name) { Clp_Internal *cli = clp->internal; int optno = cli->current_option; return optno >= 0 && strcmp(cli->opt[optno].long_name, long_name) == 0; } int Clp_IsShort(Clp_Parser *clp, int short_name) { Clp_Internal *cli = clp->internal; int optno = cli->current_option; return optno >= 0 && cli->opt[optno].short_name == short_name; } #ifdef __cplusplus } #endif gifsicle-1.96/src/fmalloc.c000066400000000000000000000015131475770763400156320ustar00rootroot00000000000000#if HAVE_CONFIG_H # include #endif #include #include #include #ifdef __cplusplus extern "C" { #endif extern const char* program_name; void* Gif_Realloc(void* p, size_t s, size_t n, const char* file, int line) { (void) file, (void) line; if (s == 0 || n == 0) { Gif_Free(p); return (void*) 0; } else if (s == 1 || n == 1 || s <= ((size_t) -1) / n) { p = realloc(p, s * n); if (!p) { fprintf(stderr, "%s: Out of memory, giving up\n", program_name); exit(1); } return p; } else { fprintf(stderr, "%s: Out of memory, giving up (huge allocation)\n", program_name); exit(1); return (void*) 0; } } #undef Gif_Free void Gif_Free(void* p) { free(p); } #ifdef __cplusplus } #endif gifsicle-1.96/src/gifdiff.c000066400000000000000000000452621475770763400156240ustar00rootroot00000000000000/* gifdiff.c - Gifdiff compares GIF images for identical appearance. Copyright (C) 1998-2025 Eddie Kohler, ekohler@gmail.com This file is part of gifdiff, in the gifsicle package. Gifdiff is free software. It is distributed under the GNU Public License, version 2; you can copy, distribute, or alter it at will, as long as this notice is kept intact and this source code is made available. There is no warranty, express or implied. */ #include #include #include #include #include #include #include #if HAVE_UNISTD_H # include #endif #define QUIET_OPT 300 #define HELP_OPT 301 #define VERSION_OPT 302 #define IGNORE_REDUNDANCY_OPT 303 #define REDUNDANCY_OPT 304 #define IGNORE_BACKGROUND_OPT 305 #define BACKGROUND_OPT 306 const Clp_Option options[] = { { "help", 'h', HELP_OPT, 0, 0 }, { "brief", 'q', QUIET_OPT, 0, Clp_Negate }, { "redundancy", 0, REDUNDANCY_OPT, 0, Clp_Negate }, { "ignore-redundancy", 'w', IGNORE_REDUNDANCY_OPT, 0, Clp_Negate }, { "bg", 0, BACKGROUND_OPT, 0, Clp_Negate }, { "ignore-bg", 0, IGNORE_BACKGROUND_OPT, 0, Clp_Negate }, { "background", 0, BACKGROUND_OPT, 0, Clp_Negate }, { "ignore-background", 'B', IGNORE_BACKGROUND_OPT, 0, Clp_Negate }, { "version", 'v', VERSION_OPT, 0, 0 } }; const char *program_name; static const char *filename1; static const char *filename2; static unsigned screen_width, screen_height; #define TRANSP (0) static uint16_t *gdata[2]; static uint16_t *glast[2]; static uint16_t *scratch; static uint16_t *line; static int brief; static int ignore_redundancy; static int ignore_background; static Clp_Parser* clp; static void combine_colormaps(Gif_Colormap *gfcm, Gif_Colormap *newcm) { int i, gfcm_ncol = gfcm ? gfcm->ncol : 0; for (i = 0; i < gfcm_ncol; i++) { Gif_Color *c = &gfcm->col[i]; c->pixel = Gif_AddColor(newcm, c, 1); } } static void fill_area(uint16_t *data, int l, int t, int w, int h, uint16_t val) { int x; data += screen_width * t + l; for (; h > 0; --h) { for (x = w; x > 0; --x) *data++ = val; data += screen_width - w; } } static void copy_area(uint16_t *dst, const uint16_t *src, int l, int t, int w, int h) { dst += screen_width * t + l; src += screen_width * t + l; for (; h > 0; --h, dst += screen_width, src += screen_width) memcpy(dst, src, sizeof(uint16_t) * w); } static void expand_bounds(int *lf, int *tp, int *rt, int *bt, const Gif_Image *gfi) { int empty = (*lf >= *rt || *tp >= *bt); if (empty || gfi->left < *lf) *lf = gfi->left; if (empty || gfi->top < *tp) *tp = gfi->top; if (empty || gfi->left + gfi->width > *rt) *rt = gfi->left + gfi->width; if (empty || gfi->top + gfi->height > *bt) *bt = gfi->top + gfi->height; } static int apply_image(int is_second, Gif_Stream *gfs, int imageno, uint16_t background) { int i, x, y, any_change; Gif_Image *gfi = gfs->images[imageno]; Gif_Image *pgfi = imageno ? gfs->images[imageno - 1] : 0; int width = gfi->width; uint16_t map[256]; uint16_t *data = gdata[is_second]; uint16_t *last = glast[is_second]; Gif_Colormap *gfcm = gfi->local ? gfi->local : gfs->global; int gfcm_ncol = gfcm ? gfcm->ncol : 0; /* set up colormap */ for (i = 0; i < gfcm_ncol; ++i) map[i] = gfcm->col[i].pixel; for (i = gfcm_ncol; i < 256; ++i) map[i] = 1; if (gfi->transparent >= 0 && gfi->transparent < 256) map[gfi->transparent] = TRANSP; /* if this image's disposal is 'previous', save the post-disposal version in 'scratch' */ if (gfi->disposal == GIF_DISPOSAL_PREVIOUS) { copy_area(scratch, data, gfi->left, gfi->top, gfi->width, gfi->height); if (pgfi && pgfi->disposal == GIF_DISPOSAL_PREVIOUS) copy_area(scratch, last, pgfi->left, pgfi->top, pgfi->width, pgfi->height); else if (pgfi && pgfi->disposal == GIF_DISPOSAL_BACKGROUND) fill_area(scratch, pgfi->left, pgfi->top, pgfi->width, pgfi->height, background); } /* uncompress and clip */ Gif_UncompressImage(gfs, gfi); Gif_ClipImage(gfi, 0, 0, screen_width, screen_height); any_change = imageno == 0; { int lf = 0, tp = 0, rt = 0, bt = 0; expand_bounds(&lf, &tp, &rt, &bt, gfi); if (pgfi && pgfi->disposal == GIF_DISPOSAL_PREVIOUS) expand_bounds(&lf, &tp, &rt, &bt, pgfi); else if (pgfi && pgfi->disposal == GIF_DISPOSAL_BACKGROUND) { expand_bounds(&lf, &tp, &rt, &bt, pgfi); fill_area(last, pgfi->left, pgfi->top, pgfi->width, pgfi->height, background); } else pgfi = 0; for (y = tp; y < bt; ++y) { uint16_t *outd = data + screen_width * y + lf; if (!any_change) memcpy(line, outd, (rt - lf) * sizeof(uint16_t)); if (pgfi && y >= pgfi->top && y < pgfi->top + pgfi->height) memcpy(outd + pgfi->left - lf, last + screen_width * y + pgfi->left, pgfi->width * sizeof(uint16_t)); if (y >= gfi->top && y < gfi->top + gfi->height) { uint16_t *xoutd = outd + gfi->left - lf; const uint8_t *ind = gfi->img[y - gfi->top]; for (x = 0; x < width; ++x, ++ind, ++xoutd) if (map[*ind] != TRANSP) *xoutd = map[*ind]; } if (!any_change && memcmp(line, outd, (rt - lf) * sizeof(uint16_t)) != 0) any_change = 1; } } Gif_ReleaseUncompressedImage(gfi); Gif_ReleaseCompressedImage(gfi); /* switch 'glast' with 'scratch' if necessary */ if (gfi->disposal == GIF_DISPOSAL_PREVIOUS) { uint16_t *x = scratch; scratch = glast[is_second]; glast[is_second] = x; } return any_change; } #define SAME 0 #define DIFFERENT 1 static int was_different; static void different(const char *format, ...) { va_list val; va_start(val, format); if (!brief) { vfprintf(stdout, format, val); fputc('\n', stdout); } va_end(val); was_different = 1; } static void name_loopcount(int loopcount, char *buf, size_t bufsz) { if (loopcount < 0) strcpy(buf, "none"); else if (loopcount == 0) strcpy(buf, "forever"); else snprintf(buf, bufsz, "%d", loopcount); } static void name_delay(int delay, char *buf, size_t bufsz) { if (delay == 0) strcpy(buf, "none"); else snprintf(buf, bufsz, "%d.%02ds", delay / 100, delay % 100); } static void name_color(int color, Gif_Colormap *gfcm, char *buf, size_t bufsz) { if (color == TRANSP) strcpy(buf, "transparent"); else { Gif_Color *c = &gfcm->col[color]; snprintf(buf, bufsz, "#%02X%02X%02X", c->gfc_red, c->gfc_green, c->gfc_blue); } } int compare(Gif_Stream *s1, Gif_Stream *s2) { Gif_Colormap *newcm; int imageno1, imageno2, background1, background2; char buf1[256], buf2[256], fbuf[256]; was_different = 0; /* Compare image counts and screen sizes. If either of these differs, quit early. */ Gif_CalculateScreenSize(s1, 0); Gif_CalculateScreenSize(s2, 0); if (s1->screen_width != s2->screen_width || s1->screen_height != s2->screen_height) { different("screen sizes differ: <%dx%d >%dx%d", s1->screen_width, s1->screen_height, s2->screen_width, s2->screen_height); return DIFFERENT; } if (s1->screen_width == 0 || s1->screen_height == 0 || s2->screen_width == 0 || s2->screen_height == 0) { /* paranoia -- don't think this can happen */ different("zero screen sizes"); return DIFFERENT; } if (s1->nimages == 0 || s2->nimages == 0) { if (s1->nimages != s2->nimages) { different("frame counts differ: <#%d >#%d", s1->nimages, s2->nimages); return DIFFERENT; } else return SAME; } /* Create arrays for the image data */ screen_width = s1->screen_width; screen_height = s1->screen_height; gdata[0] = Gif_NewArray(uint16_t, screen_width * screen_height); gdata[1] = Gif_NewArray(uint16_t, screen_width * screen_height); glast[0] = Gif_NewArray(uint16_t, screen_width * screen_height); glast[1] = Gif_NewArray(uint16_t, screen_width * screen_height); scratch = Gif_NewArray(uint16_t, screen_width * screen_height); line = Gif_NewArray(uint16_t, screen_width); /* Merge all distinct colors from the two images into one colormap, setting the 'pixel' slots in the images' colormaps to the corresponding values in the merged colormap. Don't forget transparency */ newcm = Gif_NewFullColormap(1, 256); combine_colormaps(s1->global, newcm); combine_colormaps(s2->global, newcm); for (imageno1 = 0; imageno1 < s1->nimages; ++imageno1) combine_colormaps(s1->images[imageno1]->local, newcm); for (imageno2 = 0; imageno2 < s2->nimages; ++imageno2) combine_colormaps(s2->images[imageno2]->local, newcm); /* Choose the background values */ background1 = background2 = TRANSP; if ((s1->nimages == 0 || s1->images[0]->transparent < 0) && s1->global && s1->background < s1->global->ncol) background1 = s1->global->col[ s1->background ].pixel; if ((s2->nimages == 0 || s2->images[0]->transparent < 0) && s2->global && s2->background < s2->global->ncol) background2 = s2->global->col[ s2->background ].pixel; /* Clear screens */ fill_area(gdata[0], 0, 0, screen_width, screen_height, TRANSP); fill_area(gdata[1], 0, 0, screen_width, screen_height, TRANSP); /* Loopcounts differ? */ if (s1->loopcount != s2->loopcount) { name_loopcount(s1->loopcount, buf1, sizeof(buf1)); name_loopcount(s2->loopcount, buf2, sizeof(buf2)); different("loop counts differ: <%s >%s", buf1, buf2); } /* Loop over frames, comparing image data and delays */ apply_image(0, s1, 0, background1); apply_image(1, s2, 0, background2); imageno1 = imageno2 = 0; while (imageno1 != s1->nimages && imageno2 != s2->nimages) { int fi1 = imageno1, fi2 = imageno2, delay1 = s1->images[fi1]->delay, delay2 = s2->images[fi2]->delay; /* get message right */ if (imageno1 == imageno2) snprintf(fbuf, sizeof(fbuf), "#%d", imageno1); else snprintf(fbuf, sizeof(fbuf), "<#%d >#%d", imageno1, imageno2); /* compare pixels */ if (memcmp(gdata[0], gdata[1], screen_width * screen_height * sizeof(uint16_t)) != 0) { unsigned d, c = screen_width * screen_height; uint16_t *d1 = gdata[0], *d2 = gdata[1]; for (d = 0; d < c; d++, d1++, d2++) if (*d1 != *d2) { name_color(*d1, newcm, buf1, sizeof(buf1)); name_color(*d2, newcm, buf2, sizeof(buf2)); different("frame %s pixels differ: %d,%d <%s >%s", fbuf, d % screen_width, d / screen_width, buf1, buf2); break; } } /* compare background */ if (!ignore_background && background1 != background2 && (imageno1 == 0 || s1->images[imageno1 - 1]->disposal == GIF_DISPOSAL_BACKGROUND) && (imageno2 == 0 || s2->images[imageno2 - 1]->disposal == GIF_DISPOSAL_BACKGROUND)) { unsigned d, c = screen_width * screen_height; uint16_t *d1 = gdata[0], *d2 = gdata[1]; for (d = 0; d < c; ++d, ++d1, ++d2) if (*d1 == TRANSP || *d2 == TRANSP) { name_color(background1, newcm, buf1, sizeof(buf1)); name_color(background2, newcm, buf2, sizeof(buf2)); different("frame %s background pixels differ: %d,%d <%s >%s", fbuf, d % screen_width, d / screen_width, buf1, buf2); background1 = background2 = TRANSP; break; } } /* move to next images, skipping redundancy */ for (++imageno1; imageno1 < s1->nimages && !apply_image(0, s1, imageno1, background1); ++imageno1) delay1 += s1->images[imageno1]->delay; for (++imageno2; imageno2 < s2->nimages && !apply_image(1, s2, imageno2, background2); ++imageno2) delay2 += s2->images[imageno2]->delay; if (!ignore_redundancy) { fi1 = (imageno1 - fi1) - (imageno2 - fi2); for (; fi1 > 0; --fi1) different("extra redundant frame: <#%d", imageno1 - fi1); for (; fi1 < 0; ++fi1) different("extra redundant frame: >#%d", imageno2 + fi1); } if (delay1 != delay2) { name_delay(delay1, buf1, sizeof(buf1)); name_delay(delay2, buf2, sizeof(buf2)); different("frame %s delays differ: <%s >%s", fbuf, buf1, buf2); } } if (imageno1 != s1->nimages || imageno2 != s2->nimages) different("frame counts differ: <#%d >#%d", s1->nimages, s2->nimages); /* That's it! */ Gif_DeleteColormap(newcm); Gif_DeleteArray(gdata[0]); Gif_DeleteArray(gdata[1]); Gif_DeleteArray(glast[0]); Gif_DeleteArray(glast[1]); Gif_DeleteArray(scratch); Gif_DeleteArray(line); return was_different ? DIFFERENT : SAME; } void short_usage(void) { Clp_fprintf(clp, stderr, "Usage: %s [OPTION]... FILE1 FILE2\n\ Try %<%s --help%> for more information.\n", program_name, program_name); } void usage(void) { Clp_fprintf(clp, stdout, "\ % compares two GIF files (either images or animations) for identical\n\ visual appearance. An animation and an optimized version of the same animation\n\ should compare as the same. Gifdiff exits with status 0 if the images are\n\ the same, 1 if they%,re different, and 2 if there was some error.\n\ \n\ Usage: %s [OPTION]... FILE1 FILE2\n\n", program_name); Clp_fprintf(clp, stdout, "\ Options:\n\ -q, --brief Don%,t report detailed differences.\n\ -w, --ignore-redundancy Ignore differences in redundant frames.\n\ -B, --ignore-background Ignore differences in background colors.\n\ -h, --help Print this message and exit.\n\ -v, --version Print version number and exit.\n\ \n\ Report bugs to .\n"); } void fatal_error(const char* format, ...) { char buf[BUFSIZ]; int n = snprintf(buf, BUFSIZ, "%s: ", program_name); va_list val; va_start(val, format); Clp_vsnprintf(clp, buf + n, BUFSIZ - n, format, val); va_end(val); fputs(buf, stderr); exit(2); /* exit(2) for trouble */ } void error(const char* format, ...) { char buf[BUFSIZ]; int n = snprintf(buf, BUFSIZ, "%s: ", program_name); va_list val; va_start(val, format); Clp_vsnprintf(clp, buf + n, BUFSIZ - n, format, val); va_end(val); fputs(buf, stderr); } static int gifread_error_count; static void gifread_error(Gif_Stream* gfs, Gif_Image* gfi, int is_error, const char *message) { static int last_is_error = 0; static int last_which_image = 0; static char last_message[256]; static int different_error_count = 0; static int same_error_count = 0; int which_image = Gif_ImageNumber(gfs, gfi); const char *filename = gfs->landmark; if (which_image < 0) which_image = gfs->nimages; if (gifread_error_count == 0) { last_which_image = -1; last_message[0] = 0; different_error_count = 0; } gifread_error_count++; if (last_message[0] && different_error_count <= 10 && (last_which_image != which_image || message == 0 || strcmp(message, last_message) != 0)) { const char *etype = last_is_error ? "error" : "warning"; error("While reading %<%s%> frame #%d:\n", filename, last_which_image); if (same_error_count == 1) error(" %s: %s\n", etype, last_message); else if (same_error_count > 0) error(" %s: %s (%d times)\n", etype, last_message, same_error_count); same_error_count = 0; last_message[0] = 0; } if (message) { if (last_message[0] == 0) different_error_count++; same_error_count++; strcpy(last_message, message); last_which_image = which_image; last_is_error = is_error; } else last_message[0] = 0; if (different_error_count == 11 && message) { error("(more errors while reading %<%s%>)\n", filename); different_error_count++; } } static Gif_Stream * read_stream(const char **filename) { FILE *f; Gif_Stream *gfs; if (*filename == 0) { #if 0 /* Since gifdiff always takes explicit filename arguments, allow explicit reads from terminal. */ #ifndef OUTPUT_GIF_TO_TERMINAL if (isatty(fileno(stdin))) { fatal_error(": is a terminal\n"); return NULL; } #endif #endif f = stdin; #if defined(_MSDOS) || defined(_WIN32) _setmode(_fileno(stdin), _O_BINARY); #elif defined(__DJGPP__) setmode(fileno(stdin), O_BINARY); #elif defined(__EMX__) _fsetmode(stdin, "b"); #endif *filename = ""; } else { f = fopen(*filename, "rb"); if (!f) fatal_error("%s: %s\n", *filename, strerror(errno)); } gifread_error_count = 0; gfs = Gif_FullReadFile(f, GIF_READ_COMPRESSED, *filename, gifread_error); if (!gfs) fatal_error("%s: file not in GIF format\n", *filename); return gfs; } int main(int argc, char *argv[]) { int how_many_inputs = 0; int status; const char **inputp; Gif_Stream *gfs1, *gfs2; clp = Clp_NewParser(argc, (const char * const *)argv, sizeof(options) / sizeof(options[0]), options); program_name = Clp_ProgramName(clp); brief = 0; while (1) { int opt = Clp_Next(clp); switch (opt) { case HELP_OPT: usage(); exit(0); break; case VERSION_OPT: printf("gifdiff (LCDF Gifsicle) %s\n", VERSION); printf("Copyright (C) 1998-2025 Eddie Kohler\n\ This is free software; see the source for copying conditions.\n\ There is NO warranty, not even for merchantability or fitness for a\n\ particular purpose.\n"); exit(0); break; case QUIET_OPT: brief = !clp->negated; break; case IGNORE_REDUNDANCY_OPT: ignore_redundancy = !clp->negated; break; case REDUNDANCY_OPT: ignore_redundancy = !!clp->negated; break; case IGNORE_BACKGROUND_OPT: ignore_background = !clp->negated; break; case BACKGROUND_OPT: ignore_background = !!clp->negated; break; case Clp_NotOption: if (how_many_inputs == 2) { error("too many file arguments\n"); goto bad_option; } inputp = (how_many_inputs == 0 ? &filename1 : &filename2); how_many_inputs++; if (strcmp(clp->vstr, "-") == 0) *inputp = 0; else *inputp = clp->vstr; break; bad_option: case Clp_BadOption: short_usage(); exit(1); break; case Clp_Done: goto done; } } done: if (how_many_inputs < 2) fatal_error("need exactly 2 file arguments\n"); if (filename1 == 0 && filename2 == 0) fatal_error("can%,t read both files from stdin\n"); gfs1 = read_stream(&filename1); gfs2 = read_stream(&filename2); status = (compare(gfs1, gfs2) == DIFFERENT); if (status == 1 && brief) printf("GIF files %s and %s differ\n", filename1, filename2); Gif_DeleteStream(gfs1); Gif_DeleteStream(gfs2); return status; } gifsicle-1.96/src/giffunc.c000066400000000000000000000437161475770763400156510ustar00rootroot00000000000000/* giffunc.c - General functions for the GIF library. Copyright (C) 1997-2021 Eddie Kohler, ekohler@gmail.com This file is part of the LCDF GIF library. The LCDF GIF library is free software. It is distributed under the GNU General Public License, version 2; you can copy, distribute, or alter it at will, as long as this notice is kept intact and this source code is made available. There is no warranty, express or implied. */ #if HAVE_CONFIG_H # include #endif #include #include #include #ifdef __cplusplus extern "C" { #endif Gif_Stream * Gif_NewStream(void) { Gif_Stream *gfs = Gif_New(Gif_Stream); if (!gfs) return 0; gfs->images = 0; gfs->nimages = gfs->imagescap = 0; gfs->global = 0; gfs->background = 256; gfs->screen_width = gfs->screen_height = 0; gfs->loopcount = -1; gfs->end_comment = 0; gfs->end_extension_list = 0; gfs->errors = 0; gfs->user_flags = 0; gfs->refcount = 0; gfs->landmark = 0; return gfs; } Gif_Image * Gif_NewImage(void) { Gif_Image *gfi = Gif_New(Gif_Image); if (!gfi) return 0; gfi->width = gfi->height = 0; gfi->img = 0; gfi->image_data = 0; gfi->left = gfi->top = 0; gfi->delay = 0; gfi->disposal = GIF_DISPOSAL_NONE; gfi->interlace = 0; gfi->local = 0; gfi->transparent = -1; gfi->user_flags = 0; gfi->identifier = 0; gfi->comment = 0; gfi->extension_list = 0; gfi->free_image_data = Gif_Free; gfi->compressed_len = 0; gfi->compressed_errors = 0; gfi->compressed = 0; gfi->free_compressed = 0; gfi->user_data = 0; gfi->free_user_data = 0; gfi->refcount = 0; return gfi; } Gif_Colormap * Gif_NewColormap(void) { return Gif_NewFullColormap(0, 256); } Gif_Colormap * Gif_NewFullColormap(int count, int capacity) { Gif_Colormap *gfcm = Gif_New(Gif_Colormap); if (!gfcm || count < 0) { Gif_Delete(gfcm); return 0; } if (count > capacity) capacity = count; if (capacity < 256) capacity = 256; gfcm->ncol = count; gfcm->capacity = capacity; gfcm->col = Gif_NewArray(Gif_Color, capacity); gfcm->refcount = 0; gfcm->user_flags = 0; if (!gfcm->col) { Gif_Delete(gfcm); return 0; } return gfcm; } Gif_Comment * Gif_NewComment(void) { Gif_Comment *gfcom = Gif_New(Gif_Comment); if (!gfcom) return 0; gfcom->str = 0; gfcom->len = 0; gfcom->count = gfcom->cap = 0; return gfcom; } Gif_Extension * Gif_NewExtension(int kind, const char* appname, int applength) { Gif_Extension *gfex = Gif_New(Gif_Extension); if (!gfex) return 0; gfex->kind = kind; if (appname) { gfex->appname = (char*) Gif_NewArray(char, applength + 1); if (!gfex->appname) { Gif_Delete(gfex); return 0; } memcpy(gfex->appname, appname, applength); gfex->appname[applength] = 0; gfex->applength = applength; } else { gfex->appname = 0; gfex->applength = 0; } gfex->data = 0; gfex->stream = 0; gfex->image = 0; gfex->next = 0; gfex->free_data = 0; gfex->packetized = 0; return gfex; } Gif_Extension* Gif_CopyExtension(Gif_Extension* src) { Gif_Extension* dst = Gif_NewExtension(src->kind, src->appname, src->applength); if (!dst) return NULL; if (!src->data || !src->free_data) { dst->data = src->data; dst->length = src->length; } else { dst->data = Gif_NewArray(uint8_t, src->length); if (!dst->data) { Gif_DeleteExtension(dst); return NULL; } memcpy(dst->data, src->data, src->length); dst->length = src->length; dst->free_data = Gif_Free; } dst->packetized = src->packetized; return dst; } char * Gif_CopyString(const char *s) { int l; char *copy; if (!s) return 0; l = strlen(s); copy = Gif_NewArray(char, l + 1); if (!copy) return 0; memcpy(copy, s, l + 1); return copy; } int Gif_AddImage(Gif_Stream *gfs, Gif_Image *gfi) { if (gfs->nimages >= gfs->imagescap) { if (gfs->imagescap) gfs->imagescap *= 2; else gfs->imagescap = 2; Gif_ReArray(gfs->images, Gif_Image *, gfs->imagescap); if (!gfs->images) return 0; } gfs->images[gfs->nimages] = gfi; gfs->nimages++; gfi->refcount++; return 1; } void Gif_RemoveImage(Gif_Stream *gfs, int inum) { int j; if (inum < 0 || inum >= gfs->nimages) return; Gif_DeleteImage(gfs->images[inum]); for (j = inum; j < gfs->nimages - 1; j++) gfs->images[j] = gfs->images[j+1]; gfs->nimages--; } int Gif_ImageColorBound(const Gif_Image* gfi) { if (gfi->compressed && gfi->compressed[0] > 0 && gfi->compressed[0] < 8) return 1 << gfi->compressed[0]; else return 256; } int Gif_AddCommentTake(Gif_Comment *gfcom, char *x, int xlen) { if (gfcom->count >= gfcom->cap) { if (gfcom->cap) gfcom->cap *= 2; else gfcom->cap = 2; Gif_ReArray(gfcom->str, char *, gfcom->cap); Gif_ReArray(gfcom->len, int, gfcom->cap); if (!gfcom->str || !gfcom->len) return 0; } if (xlen < 0) xlen = strlen(x); gfcom->str[ gfcom->count ] = x; gfcom->len[ gfcom->count ] = xlen; gfcom->count++; return 1; } int Gif_AddComment(Gif_Comment *gfcom, const char *x, int xlen) { char *new_x; if (xlen < 0) xlen = strlen(x); new_x = Gif_NewArray(char, xlen); if (!new_x) return 0; memcpy(new_x, x, xlen); if (Gif_AddCommentTake(gfcom, new_x, xlen) == 0) { Gif_DeleteArray(new_x); return 0; } else return 1; } int Gif_AddExtension(Gif_Stream* gfs, Gif_Image* gfi, Gif_Extension* gfex) { Gif_Extension **pprev; if (gfex->stream || gfex->image) return 0; pprev = gfi ? &gfi->extension_list : &gfs->end_extension_list; while (*pprev) pprev = &(*pprev)->next; *pprev = gfex; gfex->stream = gfs; gfex->image = gfi; gfex->next = 0; return 1; } int Gif_ImageNumber(Gif_Stream *gfs, Gif_Image *gfi) { int i; if (gfs && gfi) for (i = 0; i != gfs->nimages; ++i) if (gfs->images[i] == gfi) return i; return -1; } void Gif_CalculateScreenSize(Gif_Stream *gfs, int force) { int i; int screen_width = force ? 0 : gfs->screen_width; int screen_height = force ? 0 : gfs->screen_height; for (i = 0; i < gfs->nimages && (force || i == 0); ++i) { Gif_Image *gfi = gfs->images[i]; if (screen_width < gfi->left + gfi->width) screen_width = gfi->left + gfi->width; if (screen_height < gfi->top + gfi->height) screen_height = gfi->top + gfi->height; } if (gfs->screen_width < screen_width || force) gfs->screen_width = screen_width; if (gfs->screen_height < screen_height || force) gfs->screen_height = screen_height; } Gif_Stream * Gif_CopyStreamSkeleton(Gif_Stream *gfs) { Gif_Stream *ngfs = Gif_NewStream(); if (!ngfs) return 0; ngfs->global = Gif_CopyColormap(gfs->global); ngfs->background = gfs->background; ngfs->screen_width = gfs->screen_width; ngfs->screen_height = gfs->screen_height; ngfs->loopcount = gfs->loopcount; if (gfs->global && !ngfs->global) { Gif_DeleteStream(ngfs); return 0; } else return ngfs; } Gif_Stream * Gif_CopyStreamImages(Gif_Stream *gfs) { Gif_Stream *ngfs = Gif_CopyStreamSkeleton(gfs); int i; if (!ngfs) return 0; for (i = 0; i < gfs->nimages; i++) { Gif_Image *gfi = Gif_CopyImage(gfs->images[i]); if (!gfi || !Gif_AddImage(ngfs, gfi)) { Gif_DeleteStream(ngfs); return 0; } } return ngfs; } Gif_Colormap * Gif_CopyColormap(Gif_Colormap *src) { Gif_Colormap *dest; if (!src) return 0; dest = Gif_NewFullColormap(src->ncol, src->capacity); if (!dest) return 0; memcpy(dest->col, src->col, sizeof(src->col[0]) * src->ncol); return dest; } Gif_Image * Gif_CopyImage(Gif_Image *src) { Gif_Image *dest; uint8_t *data; int i; if (!src) return 0; dest = Gif_NewImage(); if (!dest) return 0; dest->identifier = Gif_CopyString(src->identifier); if (!dest->identifier && src->identifier) goto failure; if (src->comment) { dest->comment = Gif_NewComment(); if (!dest->comment) goto failure; for (i = 0; i < src->comment->count; i++) if (!Gif_AddComment(dest->comment, src->comment->str[i], src->comment->len[i])) goto failure; } if (src->extension_list) { Gif_Extension* gfex = src->extension_list; while (gfex) { Gif_Extension* dstex = Gif_CopyExtension(gfex); if (!dstex) goto failure; Gif_AddExtension(NULL, dest, dstex); gfex = gfex->next; } } dest->local = Gif_CopyColormap(src->local); if (!dest->local && src->local) goto failure; dest->transparent = src->transparent; dest->delay = src->delay; dest->disposal = src->disposal; dest->left = src->left; dest->top = src->top; dest->width = src->width; dest->height = src->height; dest->interlace = src->interlace; if (src->img) { dest->img = Gif_NewArray(uint8_t *, dest->height + 1); dest->image_data = Gif_NewArray(uint8_t, (size_t) dest->width * (size_t) dest->height); dest->free_image_data = Gif_Free; if (!dest->img || !dest->image_data) goto failure; for (i = 0, data = dest->image_data; i < dest->height; i++) { memcpy(data, src->img[i], dest->width); dest->img[i] = data; data += dest->width; } dest->img[dest->height] = 0; } if (src->compressed) { if (src->free_compressed == 0) dest->compressed = src->compressed; else { dest->compressed = Gif_NewArray(uint8_t, src->compressed_len); dest->free_compressed = Gif_Free; memcpy(dest->compressed, src->compressed, src->compressed_len); } dest->compressed_len = src->compressed_len; dest->compressed_errors = src->compressed_errors; } return dest; failure: Gif_DeleteImage(dest); return 0; } void Gif_MakeImageEmpty(Gif_Image* gfi) { Gif_ReleaseUncompressedImage(gfi); Gif_ReleaseCompressedImage(gfi); gfi->left = gfi->left < 0xFFFE ? gfi->left : 0xFFFE; gfi->top = gfi->top < 0xFFFE ? gfi->top : 0xFFFE; gfi->width = 1; gfi->height = 1; gfi->transparent = 0; Gif_CreateUncompressedImage(gfi, 0); gfi->img[0][0] = 0; } /** DELETION **/ typedef struct Gif_DeletionHook { int kind; Gif_DeletionHookFunc func; void *callback_data; struct Gif_DeletionHook *next; } Gif_DeletionHook; static Gif_DeletionHook *all_hooks; void Gif_DeleteStream(Gif_Stream *gfs) { Gif_DeletionHook *hook; int i; if (!gfs || --gfs->refcount > 0) return; for (i = 0; i < gfs->nimages; i++) Gif_DeleteImage(gfs->images[i]); Gif_DeleteArray(gfs->images); Gif_DeleteColormap(gfs->global); Gif_DeleteComment(gfs->end_comment); while (gfs->end_extension_list) Gif_DeleteExtension(gfs->end_extension_list); for (hook = all_hooks; hook; hook = hook->next) if (hook->kind == GIF_T_STREAM) (*hook->func)(GIF_T_STREAM, gfs, hook->callback_data); Gif_Delete(gfs); } void Gif_DeleteImage(Gif_Image *gfi) { Gif_DeletionHook *hook; if (!gfi || --gfi->refcount > 0) return; for (hook = all_hooks; hook; hook = hook->next) if (hook->kind == GIF_T_IMAGE) (*hook->func)(GIF_T_IMAGE, gfi, hook->callback_data); Gif_DeleteArray(gfi->identifier); Gif_DeleteComment(gfi->comment); while (gfi->extension_list) Gif_DeleteExtension(gfi->extension_list); Gif_DeleteColormap(gfi->local); if (gfi->image_data && gfi->free_image_data) (*gfi->free_image_data)((void *)gfi->image_data); Gif_DeleteArray(gfi->img); if (gfi->compressed && gfi->free_compressed) (*gfi->free_compressed)((void *)gfi->compressed); if (gfi->user_data && gfi->free_user_data) (*gfi->free_user_data)(gfi->user_data); Gif_Delete(gfi); } void Gif_DeleteColormap(Gif_Colormap *gfcm) { Gif_DeletionHook *hook; if (!gfcm || --gfcm->refcount > 0) return; for (hook = all_hooks; hook; hook = hook->next) if (hook->kind == GIF_T_COLORMAP) (*hook->func)(GIF_T_COLORMAP, gfcm, hook->callback_data); Gif_DeleteArray(gfcm->col); Gif_Delete(gfcm); } void Gif_DeleteComment(Gif_Comment *gfcom) { int i; if (!gfcom) return; for (i = 0; i < gfcom->count; i++) Gif_DeleteArray(gfcom->str[i]); Gif_DeleteArray(gfcom->str); Gif_DeleteArray(gfcom->len); Gif_Delete(gfcom); } void Gif_DeleteExtension(Gif_Extension *gfex) { if (!gfex) return; if (gfex->data && gfex->free_data) (*gfex->free_data)(gfex->data); Gif_DeleteArray(gfex->appname); if (gfex->stream || gfex->image) { Gif_Extension** pprev; if (gfex->image) pprev = &gfex->image->extension_list; else pprev = &gfex->stream->end_extension_list; while (*pprev && *pprev != gfex) pprev = &(*pprev)->next; if (*pprev) *pprev = gfex->next; } Gif_Delete(gfex); } /** DELETION HOOKS **/ int Gif_AddDeletionHook(int kind, void (*func)(int, void *, void *), void *cb) { Gif_DeletionHook *hook = Gif_New(Gif_DeletionHook); if (!hook) return 0; Gif_RemoveDeletionHook(kind, func, cb); hook->kind = kind; hook->func = func; hook->callback_data = cb; hook->next = all_hooks; all_hooks = hook; return 1; } void Gif_RemoveDeletionHook(int kind, void (*func)(int, void *, void *), void *cb) { Gif_DeletionHook *hook = all_hooks, *prev = 0; while (hook) { if (hook->kind == kind && hook->func == func && hook->callback_data == cb) { if (prev) prev->next = hook->next; else all_hooks = hook->next; Gif_Delete(hook); return; } prev = hook; hook = hook->next; } } int Gif_ColorEq(Gif_Color *c1, Gif_Color *c2) { return GIF_COLOREQ(c1, c2); } int Gif_FindColor(Gif_Colormap *gfcm, Gif_Color *c) { int i; for (i = 0; i < gfcm->ncol; i++) if (GIF_COLOREQ(&gfcm->col[i], c)) return i; return -1; } int Gif_AddColor(Gif_Colormap *gfcm, Gif_Color *c, int look_from) { int i; if (look_from >= 0) for (i = look_from; i < gfcm->ncol; i++) if (GIF_COLOREQ(&gfcm->col[i], c)) return i; if (gfcm->ncol >= gfcm->capacity) { gfcm->capacity *= 2; Gif_ReArray(gfcm->col, Gif_Color, gfcm->capacity); if (gfcm->col == 0) return -1; } i = gfcm->ncol; gfcm->ncol++; gfcm->col[i] = *c; return i; } Gif_Image * Gif_GetImage(Gif_Stream *gfs, int imagenumber) { if (imagenumber >= 0 && imagenumber < gfs->nimages) return gfs->images[imagenumber]; else return 0; } Gif_Image * Gif_GetNamedImage(Gif_Stream *gfs, const char *name) { int i; if (!name) return gfs->nimages ? gfs->images[0] : 0; for (i = 0; i < gfs->nimages; i++) if (gfs->images[i]->identifier && strcmp(gfs->images[i]->identifier, name) == 0) return gfs->images[i]; return 0; } void Gif_ReleaseCompressedImage(Gif_Image *gfi) { if (gfi->compressed && gfi->free_compressed) (*gfi->free_compressed)(gfi->compressed); gfi->compressed = 0; gfi->compressed_len = 0; gfi->compressed_errors = 0; gfi->free_compressed = 0; } void Gif_ReleaseUncompressedImage(Gif_Image *gfi) { Gif_DeleteArray(gfi->img); if (gfi->image_data && gfi->free_image_data) (*gfi->free_image_data)(gfi->image_data); gfi->img = 0; gfi->image_data = 0; gfi->free_image_data = 0; } int Gif_ClipImage(Gif_Image *gfi, int left, int top, int width, int height) { int new_width = gfi->width, new_height = gfi->height; int y; if (!gfi->img) return 0; if (gfi->left < left) { int shift = left - gfi->left; for (y = 0; y < gfi->height; y++) gfi->img[y] += shift; gfi->left += shift; new_width -= shift; } if (gfi->top < top) { int shift = top - gfi->top; for (y = gfi->height - 1; y >= shift; y++) gfi->img[y - shift] = gfi->img[y]; gfi->top += shift; new_height -= shift; } if (gfi->left + new_width >= width) new_width = width - gfi->left; if (gfi->top + new_height >= height) new_height = height - gfi->top; if (new_width < 0) new_width = 0; if (new_height < 0) new_height = 0; gfi->width = new_width; gfi->height = new_height; return 1; } int Gif_InterlaceLine(int line, int height) { height--; if (line > height / 2) return line * 2 - ( height | 1); else if (line > height / 4) return line * 4 - ((height & ~1) | 2); else if (line > height / 8) return line * 8 - ((height & ~3) | 4); else return line * 8; } int Gif_SetUncompressedImage(Gif_Image *gfi, uint8_t *image_data, void (*free_data)(void *), int data_interlaced) { /* NB does not affect compressed image (and must not) */ unsigned i; unsigned width = gfi->width; unsigned height = gfi->height; uint8_t **img; Gif_ReleaseUncompressedImage(gfi); if (!image_data) return 0; img = Gif_NewArray(uint8_t *, height + 1); if (!img) return 0; if (data_interlaced) for (i = 0; i < height; i++) img[ Gif_InterlaceLine(i, height) ] = image_data + width * i; else for (i = 0; i < height; i++) img[i] = image_data + width * i; img[height] = 0; gfi->img = img; gfi->image_data = image_data; gfi->free_image_data = free_data; return 1; } int Gif_CreateUncompressedImage(Gif_Image *gfi, int data_interlaced) { size_t sz = (size_t) gfi->width * (size_t) gfi->height; uint8_t *data = Gif_NewArray(uint8_t, sz ? sz : 1); return Gif_SetUncompressedImage(gfi, data, Gif_Free, data_interlaced); } void Gif_InitCompressInfo(Gif_CompressInfo *gcinfo) { gcinfo->flags = 0; gcinfo->loss = 0; } void Gif_Debug(char *x, ...) { va_list val; va_start(val, x); vfprintf(stderr, x, val); va_end(val); } #if !GIF_ALLOCATOR_DEFINED void* Gif_Realloc(void* p, size_t s, size_t n, const char* file, int line) { (void) file, (void) line; if (s == 0 || n == 0) Gif_Free(p); else if (s == 1 || n == 1 || s <= ((size_t) -1) / n) return realloc(p, s * n); return (void*) 0; } #undef Gif_Free void Gif_Free(void* p) { free(p); } #endif #ifdef __cplusplus } #endif gifsicle-1.96/src/gifread.c000066400000000000000000000615471475770763400156330ustar00rootroot00000000000000/* gifread.c - Functions to read GIFs. Copyright (C) 1997-2025 Eddie Kohler, ekohler@gmail.com This file is part of the LCDF GIF library. The LCDF GIF library is free software. It is distributed under the GNU General Public License, version 2; you can copy, distribute, or alter it at will, as long as this notice is kept intact and this source code is made available. There is no warranty, express or implied. */ #if HAVE_CONFIG_H # include #elif !defined(__cplusplus) /* Assume we don't have inline by default */ # define inline #endif #include #include #include #include #ifdef __cplusplus extern "C" { #endif typedef struct { Gif_Stream *stream; Gif_Code *prefix; uint8_t *suffix; uint16_t *length; uint16_t width; uint16_t height; uint8_t *image; uint8_t *maximage; unsigned decodepos; Gif_Image* gfi; Gif_ReadErrorHandler handler; int errors[2]; } Gif_Context; typedef struct Gif_Reader { FILE *f; const uint8_t *v; uint32_t pos; uint32_t length; int is_record; int is_eoi; uint8_t (*byte_getter)(struct Gif_Reader *); uint32_t (*block_getter)(uint8_t*, uint32_t, struct Gif_Reader*); int (*eofer)(struct Gif_Reader *); } Gif_Reader; static Gif_ReadErrorHandler default_error_handler = 0; #define gifgetc(grr) ((char)(*grr->byte_getter)(grr)) #define gifgetbyte(grr) ((*grr->byte_getter)(grr)) #define gifgetblock(ptr, size, grr) ((*grr->block_getter)(ptr, size, grr)) #define gifeof(grr) ((*grr->eofer)(grr)) static inline uint16_t gifgetunsigned(Gif_Reader *grr) { uint8_t one = gifgetbyte(grr); uint8_t two = gifgetbyte(grr); return one | (two << 8); } static uint8_t file_byte_getter(Gif_Reader *grr) { int i = getc(grr->f); if (i != EOF) { ++grr->pos; return i; } else return 0; } static uint32_t file_block_getter(uint8_t *p, uint32_t s, Gif_Reader *grr) { size_t nread = fread(p, 1, s, grr->f); if (nread < s) memset(p + nread, 0, s - nread); grr->pos += nread; return nread; } static int file_eofer(Gif_Reader *grr) { int c = getc(grr->f); if (c == EOF) return 1; else { ungetc(c, grr->f); return 0; } } static uint8_t record_byte_getter(Gif_Reader *grr) { if (grr->pos < grr->length) return grr->v[grr->pos++]; else return 0; } static uint32_t record_block_getter(uint8_t *p, uint32_t s, Gif_Reader *grr) { uint32_t ncopy = (grr->pos + s <= grr->length ? s : grr->length - grr->pos); memcpy(p, &grr->v[grr->pos], ncopy); grr->pos += ncopy; if (ncopy < s) memset(p + ncopy, 0, s - ncopy); return ncopy; } static int record_eofer(Gif_Reader *grr) { return grr->pos == grr->length; } static void make_data_reader(Gif_Reader *grr, const uint8_t *data, uint32_t length) { grr->v = data; grr->pos = 0; grr->length = length; grr->is_record = 1; grr->byte_getter = record_byte_getter; grr->block_getter = record_block_getter; grr->eofer = record_eofer; } static void gif_read_error(Gif_Context *gfc, int is_error, const char *text) { Gif_ReadErrorHandler handler = gfc->handler ? gfc->handler : default_error_handler; if (is_error >= 0) gfc->errors[is_error > 0] += 1; if (handler) handler(gfc->stream, gfc->gfi, is_error, text); } static uint8_t one_code(Gif_Context *gfc, Gif_Code code) { uint8_t *suffixes = gfc->suffix; Gif_Code *prefixes = gfc->prefix; uint8_t *ptr; int lastsuffix = 0; int codelength = gfc->length[code]; gfc->decodepos += codelength; ptr = gfc->image + gfc->decodepos; while (codelength > 0) { lastsuffix = suffixes[code]; code = prefixes[code]; --ptr; if (ptr < gfc->maximage) *ptr = lastsuffix; --codelength; } /* return the first pixel in the code, which, since we walked backwards through the code, was the last suffix we processed. */ return lastsuffix; } static int read_image_block(Gif_Reader *grr, uint8_t *buffer, int *bit_pos_store, int *bit_len_store, int bits_needed) { int bit_position = *bit_pos_store; int bit_length = *bit_len_store; uint8_t block_len; while (bit_position + bits_needed > bit_length) { /* Read in the next data block. */ if (bit_position >= 8) { /* Need to shift down the upper, unused part of 'buffer' */ int i = bit_position / 8; buffer[0] = buffer[i]; buffer[1] = buffer[i+1]; bit_position -= i * 8; bit_length -= i * 8; } block_len = gifgetbyte(grr); GIF_DEBUG(("\nimage_block(%d) ", block_len)); if (block_len == 0) return 0; gifgetblock(buffer + bit_length / 8, block_len, grr); bit_length += block_len * 8; } *bit_pos_store = bit_position; *bit_len_store = bit_length; return 1; } static void read_image_data(Gif_Context *gfc, Gif_Reader *grr) { /* we need a bit more than GIF_MAX_BLOCK in case a single code is split across blocks */ uint8_t buffer[GIF_MAX_BLOCK + 5]; int i; uint32_t accum; int bit_position; int bit_length; Gif_Code code; Gif_Code old_code; Gif_Code clear_code; /* eoi_code == clear_code + 1 */ Gif_Code next_code; #define CUR_BUMP_CODE (1 << bits_needed) #define CUR_CODE_MASK ((1 << bits_needed) - 1) int min_code_size; int bits_needed; gfc->decodepos = 0; min_code_size = gifgetbyte(grr); GIF_DEBUG(("\n\nmin_code_size(%d) ", min_code_size)); if (min_code_size >= GIF_MAX_CODE_BITS) { gif_read_error(gfc, 1, "image corrupted, min_code_size too big"); min_code_size = GIF_MAX_CODE_BITS - 1; } else if (min_code_size < 2) { gif_read_error(gfc, 1, "image corrupted, min_code_size too small"); min_code_size = 2; } clear_code = 1 << min_code_size; for (code = 0; code < clear_code; code++) { gfc->prefix[code] = 49428; gfc->suffix[code] = (uint8_t)code; gfc->length[code] = 1; } next_code = clear_code + 1; bits_needed = min_code_size + 1; code = clear_code; bit_length = bit_position = 0; /* Thus the 'Read in the next data block.' code below will be invoked on the first time through: exactly right! */ while (1) { old_code = code; /* GET A CODE INTO THE 'code' VARIABLE. * * 9.Dec.1998 - Rather than maintain a byte pointer and a bit offset into * the current byte (and the processing associated with that), we maintain * one number: the offset, in bits, from the beginning of 'buffer'. This * much cleaner choice was inspired by Patrick J. Naughton * 's GIF-reading code, which does the same thing. * His code distributed as part of XV in xvgif.c. */ if (bit_position + bits_needed > bit_length) /* Read in the next data block. */ if (!read_image_block(grr, buffer, &bit_position, &bit_length, bits_needed)) goto zero_length_block; i = bit_position / 8; accum = buffer[i] + (buffer[i+1] << 8); if (bits_needed >= 8) accum |= (buffer[i+2]) << 16; code = (Gif_Code)((accum >> (bit_position % 8)) & CUR_CODE_MASK); bit_position += bits_needed; GIF_DEBUG(("%d ", code)); /* CHECK FOR SPECIAL OR BAD CODES: clear_code, eoi_code, or a code that is * too large. */ if (code == clear_code) { GIF_DEBUG(("clear ")); bits_needed = min_code_size + 1; next_code = clear_code + 1; continue; } else if (code == clear_code + 1) break; else if (code > next_code && next_code && next_code != clear_code) { /* code > next_code: a (hopefully recoverable) error. Bug fix, 5/27: Do this even if old_code == clear_code, and set code to 0 to prevent errors later. (If we didn't zero code, we'd later set old_code = code; then we had old_code >= next_code; so the prefixes array got all screwed up!) Bug fix, 4/12/2010: It is not an error if next_code == clear_code. This happens at the end of a large GIF: see the next comment ("If no meaningful next code should be defined...."). */ if (gfc->errors[1] < 20) gif_read_error(gfc, 1, "image corrupted, code out of range"); else if (gfc->errors[1] == 20) gif_read_error(gfc, 1, "(not reporting more errors)"); code = 0; } /* PROCESS THE CURRENT CODE and define the next code. If no meaningful * next code should be defined, then we have set next_code to either * 'eoi_code' or 'clear_code' -- so we'll store useless prefix/suffix data * in a useless place. */ /* *First,* set up the prefix and length for the next code (in case code == next_code). */ gfc->prefix[next_code] = old_code; gfc->length[next_code] = gfc->length[old_code] + 1; /* Use one_code to process code. It's nice that it returns the first pixel in code: that's what we need. */ gfc->suffix[next_code] = one_code(gfc, code); /* Special processing if code == next_code: we didn't know code's final suffix when we called one_code, but we do now. */ /* 7.Mar.2014 -- Avoid error if image has zero width/height. */ if (code == next_code && gfc->image + gfc->decodepos <= gfc->maximage) gfc->image[gfc->decodepos - 1] = gfc->suffix[next_code]; /* Increment next_code except for the 'clear_code' special case (that's when we're reading at the end of a GIF) */ if (next_code != clear_code) { next_code++; if (next_code == CUR_BUMP_CODE) { if (bits_needed < GIF_MAX_CODE_BITS) bits_needed++; else next_code = clear_code; } } } /* read blocks until zero-length reached. */ i = gifgetbyte(grr); GIF_DEBUG(("\nafter_image(%d)\n", i)); while (i > 0) { gifgetblock(buffer, i, grr); i = gifgetbyte(grr); GIF_DEBUG(("\nafter_image(%d)\n", i)); } /* zero-length block reached. */ zero_length_block: { long delta = (long) (gfc->maximage - gfc->image) - (long) gfc->decodepos; char buf[BUFSIZ]; if (delta > 0) { snprintf(buf, sizeof(buf), "missing %ld %s of image data", delta, delta == 1 ? "pixel" : "pixels"); gif_read_error(gfc, 1, buf); memset(&gfc->image[gfc->decodepos], 0, delta); } else if (delta < -1) { /* One pixel of superfluous data is OK; that could be the code == next_code case. */ snprintf(buf, sizeof(buf), "%ld superfluous pixels of image data", -delta); gif_read_error(gfc, 0, buf); } } } static Gif_Colormap * read_color_table(int size, Gif_Reader *grr) { Gif_Colormap *gfcm = Gif_NewFullColormap(size, size); unsigned char buf[256 * 3], *bptr, *eptr; size_t n; Gif_Color *c; if (!gfcm) return 0; assert(gfcm->capacity >= 256 && size >= 0 && size <= 256); GIF_DEBUG(("colormap(%d) ", size)); n = gifgetblock(buf, size * 3, grr); /* always initialize 256 colors; colors not included in map are initialized to black in case of bad input */ memset(&buf[n], 0, 256 * 3 - n); for (bptr = buf, eptr = buf + (256 * 3), c = gfcm->col; bptr != eptr; bptr += 3, ++c) { c->gfc_red = bptr[0]; c->gfc_green = bptr[1]; c->gfc_blue = bptr[2]; c->haspixel = 0; } return gfcm; } static int read_logical_screen_descriptor(Gif_Stream *gfs, Gif_Reader *grr) /* returns 0 on memory error */ { uint8_t packed; /* we don't care about logical screen width or height */ gfs->screen_width = gifgetunsigned(grr); gfs->screen_height = gifgetunsigned(grr); packed = gifgetbyte(grr); gfs->background = gifgetbyte(grr); /* don't care about pixel aspect ratio */ gifgetbyte(grr); if (packed & 0x80) { /* have a global color table */ int ncol = 1 << ((packed & 0x07) + 1); gfs->global = read_color_table(ncol, grr); if (!gfs->global) return 0; gfs->global->refcount = 1; } else gfs->background = 256; return 1; } static int read_compressed_image(Gif_Image *gfi, Gif_Reader *grr, int read_flags) { if (grr->is_record) { const uint32_t image_pos = grr->pos; /* scan over image */ ++grr->pos; /* skip min code size */ while (grr->pos < grr->length) { int amt = grr->v[grr->pos]; grr->pos += amt + 1; if (amt == 0) break; } if (grr->pos > grr->length) grr->pos = grr->length; gfi->compressed_len = grr->pos - image_pos; gfi->compressed_errors = 0; if (read_flags & GIF_READ_CONST_RECORD) { gfi->compressed = (uint8_t*) &grr->v[image_pos]; gfi->free_compressed = 0; } else { gfi->compressed = Gif_NewArray(uint8_t, gfi->compressed_len); gfi->free_compressed = Gif_Free; if (!gfi->compressed) return 0; memcpy(gfi->compressed, &grr->v[image_pos], gfi->compressed_len); } } else { /* non-record; have to read it block by block. */ uint32_t comp_cap = 1024; uint32_t comp_len; uint8_t *comp = Gif_NewArray(uint8_t, comp_cap); int i; if (!comp) return 0; /* min code size */ i = gifgetbyte(grr); comp[0] = i; comp_len = 1; i = gifgetbyte(grr); while (i > 0) { /* add 2 before check so we don't have to check after loop when appending 0 block */ if (comp_len + i + 2 > comp_cap) { comp_cap *= 2; Gif_ReArray(comp, uint8_t, comp_cap); if (!comp) return 0; } comp[comp_len] = i; gifgetblock(comp + comp_len + 1, i, grr); comp_len += i + 1; i = gifgetbyte(grr); } comp[comp_len++] = 0; gfi->compressed_len = comp_len; gfi->compressed_errors = 0; gfi->compressed = comp; gfi->free_compressed = Gif_Free; } return 1; } static int uncompress_image(Gif_Context *gfc, Gif_Image *gfi, Gif_Reader *grr) { int old_nerrors; if (!Gif_CreateUncompressedImage(gfi, gfi->interlace)) return 0; gfc->width = gfi->width; gfc->height = gfi->height; gfc->image = gfi->image_data; gfc->maximage = gfi->image_data + (unsigned) gfi->width * (unsigned) gfi->height; old_nerrors = gfc->errors[1]; read_image_data(gfc, grr); gfi->compressed_errors = gfc->errors[1] - old_nerrors; return 1; } int Gif_FullUncompressImage(Gif_Stream* gfs, Gif_Image* gfi, Gif_ReadErrorHandler h) { Gif_Context gfc; Gif_Reader grr; int ok = 0; /* return right away if image is already uncompressed. this might screw over people who expect re-uncompressing to restore the compressed version. */ if (gfi->img) return 2; if (gfi->image_data) /* we have uncompressed data, but not an 'img' array; this shouldn't happen */ return 0; gfc.stream = gfs; gfc.gfi = gfi; gfc.prefix = Gif_NewArray(Gif_Code, GIF_MAX_CODE); gfc.suffix = Gif_NewArray(uint8_t, GIF_MAX_CODE); gfc.length = Gif_NewArray(uint16_t, GIF_MAX_CODE); gfc.handler = h; gfc.errors[0] = gfc.errors[1] = 0; if (gfc.prefix && gfc.suffix && gfc.length && gfi->compressed) { make_data_reader(&grr, gfi->compressed, gfi->compressed_len); ok = uncompress_image(&gfc, gfi, &grr); } Gif_DeleteArray(gfc.prefix); Gif_DeleteArray(gfc.suffix); Gif_DeleteArray(gfc.length); if (gfc.errors[0] || gfc.errors[1]) gif_read_error(&gfc, -1, 0); return ok && !gfc.errors[1]; } static int read_image(Gif_Reader *grr, Gif_Context *gfc, Gif_Image *gfi, int read_flags) /* returns 0 on memory error */ { uint8_t packed; gfi->left = gifgetunsigned(grr); gfi->top = gifgetunsigned(grr); gfi->width = gifgetunsigned(grr); gfi->height = gifgetunsigned(grr); /* Mainline GIF processors (Firefox, etc.) process missing width (height) as screen_width (screen_height). */ if (gfi->width == 0) gfi->width = gfc->stream->screen_width; if (gfi->height == 0) gfi->height = gfc->stream->screen_height; /* If still zero, error. */ if (gfi->width == 0 || gfi->height == 0) { gif_read_error(gfc, 1, "ignoring frame, zero width and/or height"); Gif_MakeImageEmpty(gfi); read_flags = 0; } /* If position out of range, error. */ if ((unsigned) gfi->left + (unsigned) gfi->width > 0xFFFF || (unsigned) gfi->top + (unsigned) gfi->height > 0xFFFF) { gif_read_error(gfc, 1, "ignoring frame, violates 65535x65535 screen size limit"); Gif_MakeImageEmpty(gfi); read_flags = 0; } GIF_DEBUG(("<%ux%u> ", gfi->width, gfi->height)); packed = gifgetbyte(grr); if (packed & 0x80) { /* have a local color table */ int ncol = 1 << ((packed & 0x07) + 1); gfi->local = read_color_table(ncol, grr); if (!gfi->local) return 0; gfi->local->refcount = 1; } gfi->interlace = (packed & 0x40) != 0; /* Keep the compressed data if asked */ if (read_flags & GIF_READ_COMPRESSED) { if (!read_compressed_image(gfi, grr, read_flags)) return 0; if (read_flags & GIF_READ_UNCOMPRESSED) { Gif_Reader new_grr; make_data_reader(&new_grr, gfi->compressed, gfi->compressed_len); if (!uncompress_image(gfc, gfi, &new_grr)) return 0; } } else if (read_flags & GIF_READ_UNCOMPRESSED) { if (!uncompress_image(gfc, gfi, grr)) return 0; } else { /* skip over the image */ uint8_t buffer[GIF_MAX_BLOCK]; int i = gifgetbyte(grr); while (i > 0) { gifgetblock(buffer, i, grr); i = gifgetbyte(grr); } } return 1; } static void read_graphic_control_extension(Gif_Context *gfc, Gif_Image *gfi, Gif_Reader *grr) { uint8_t len; uint8_t crap[GIF_MAX_BLOCK]; len = gifgetbyte(grr); if (len == 4) { uint8_t packed = gifgetbyte(grr); gfi->disposal = (packed >> 2) & 0x07; gfi->delay = gifgetunsigned(grr); gfi->transparent = gifgetbyte(grr); if (!(packed & 0x01)) /* transparent color doesn't exist */ gfi->transparent = -1; len -= 4; } if (len > 0) { gif_read_error(gfc, 1, "bad graphic extension"); gifgetblock(crap, len, grr); } len = gifgetbyte(grr); while (len > 0) { gif_read_error(gfc, 1, "bad graphic extension"); gifgetblock(crap, len, grr); len = gifgetbyte(grr); } } static char *last_name; static char * suck_data(char *data, int *store_len, Gif_Reader *grr) { uint8_t len = gifgetbyte(grr); int total_len = 0; while (len > 0) { Gif_ReArray(data, char, total_len + len + 1); if (!data) return 0; gifgetblock((uint8_t *)data + total_len, len, grr); total_len += len; data[total_len] = 0; len = gifgetbyte(grr); } if (store_len) *store_len = total_len; return data; } static int read_unknown_extension(Gif_Context* gfc, Gif_Reader* grr, int kind, char* appname, int applength) { uint8_t block_len = gifgetbyte(grr); uint8_t* data = 0; int data_len = 0; Gif_Extension *gfex = 0; while (block_len > 0) { Gif_ReArray(data, uint8_t, data_len + block_len + 2); if (!data) goto done; data[data_len] = block_len; gifgetblock(data + data_len + 1, block_len, grr); data_len += block_len + 1; block_len = gifgetbyte(grr); } if (data) gfex = Gif_NewExtension(kind, appname, applength); if (gfex) { gfex->data = data; gfex->free_data = Gif_Free; gfex->length = data_len; gfex->packetized = 1; data[data_len] = 0; Gif_AddExtension(gfc->stream, gfc->gfi, gfex); } done: if (!gfex) Gif_DeleteArray(data); while (block_len > 0) { uint8_t buffer[GIF_MAX_BLOCK]; gifgetblock(buffer, block_len, grr); block_len = gifgetbyte(grr); } return gfex != 0; } static int read_application_extension(Gif_Context *gfc, Gif_Reader *grr) { Gif_Stream *gfs = gfc->stream; uint8_t buffer[GIF_MAX_BLOCK + 1]; uint8_t len = gifgetbyte(grr); gifgetblock(buffer, len, grr); /* Read the Netscape loop extension. */ if (len == 11 && (memcmp(buffer, "NETSCAPE2.0", 11) == 0 || memcmp(buffer, "ANIMEXTS1.0", 11) == 0)) { len = gifgetbyte(grr); if (len == 3) { gifgetbyte(grr); /* throw away the 1 */ gfs->loopcount = gifgetunsigned(grr); len = gifgetbyte(grr); if (len) gif_read_error(gfc, 1, "bad loop extension"); } else gif_read_error(gfc, 1, "bad loop extension"); while (len > 0) { gifgetblock(buffer, len, grr); len = gifgetbyte(grr); } return 1; } else return read_unknown_extension(gfc, grr, 0xFF, (char*)buffer, len); } static int read_comment_extension(Gif_Image *gfi, Gif_Reader *grr) { int len; Gif_Comment *gfcom = gfi->comment; char *m = suck_data(0, &len, grr); if (m) { if (!gfcom) gfcom = gfi->comment = Gif_NewComment(); if (!gfcom || !Gif_AddCommentTake(gfcom, m, len)) return 0; } return 1; } static Gif_Stream * read_gif(Gif_Reader *grr, int read_flags, const char* landmark, Gif_ReadErrorHandler handler) { Gif_Stream *gfs; Gif_Image *gfi; Gif_Context gfc; int unknown_block_type = 0; if (gifgetc(grr) != 'G' || gifgetc(grr) != 'I' || gifgetc(grr) != 'F') return 0; (void)gifgetc(grr); (void)gifgetc(grr); (void)gifgetc(grr); gfs = Gif_NewStream(); gfi = Gif_NewImage(); gfc.stream = gfs; gfc.prefix = Gif_NewArray(Gif_Code, GIF_MAX_CODE); gfc.suffix = Gif_NewArray(uint8_t, GIF_MAX_CODE); gfc.length = Gif_NewArray(uint16_t, GIF_MAX_CODE); gfc.handler = handler; gfc.gfi = gfi; gfc.errors[0] = gfc.errors[1] = 0; if (!gfs || !gfi || !gfc.prefix || !gfc.suffix || !gfc.length) goto done; gfs->landmark = landmark; GIF_DEBUG(("\nGIF ")); if (!read_logical_screen_descriptor(gfs, grr)) goto done; GIF_DEBUG(("logscrdesc ")); while (!gifeof(grr)) { uint8_t block = gifgetbyte(grr); switch (block) { case ',': /* image block */ GIF_DEBUG(("imageread %d ", gfs->nimages)); gfi->identifier = last_name; last_name = 0; if (!Gif_AddImage(gfs, gfi)) goto done; else if (!read_image(grr, &gfc, gfi, read_flags)) { Gif_RemoveImage(gfs, gfs->nimages - 1); gfi = 0; goto done; } gfc.gfi = gfi = Gif_NewImage(); if (!gfi) goto done; break; case ';': /* terminator */ GIF_DEBUG(("term\n")); goto done; case '!': /* extension */ block = gifgetbyte(grr); GIF_DEBUG(("ext(0x%02X) ", block)); switch (block) { case 0xF9: read_graphic_control_extension(&gfc, gfi, grr); break; case 0xCE: last_name = suck_data(last_name, 0, grr); break; case 0xFE: if (!read_comment_extension(gfi, grr)) goto done; break; case 0xFF: read_application_extension(&gfc, grr); break; default: read_unknown_extension(&gfc, grr, block, 0, 0); break; } break; default: if (!unknown_block_type) { char buf[256]; snprintf(buf, sizeof(buf), "unknown block type %d at file offset %u", block, grr->pos - 1); gif_read_error(&gfc, 1, buf); } if (++unknown_block_type > 20) goto done; break; } } done: /* Move comments and extensions after last image into stream. */ if (gfs && gfi) { Gif_Extension* gfex; gfs->end_comment = gfi->comment; gfi->comment = 0; gfs->end_extension_list = gfi->extension_list; gfi->extension_list = 0; for (gfex = gfs->end_extension_list; gfex; gfex = gfex->next) gfex->image = NULL; } Gif_DeleteImage(gfi); Gif_DeleteArray(last_name); Gif_DeleteArray(gfc.prefix); Gif_DeleteArray(gfc.suffix); Gif_DeleteArray(gfc.length); gfc.gfi = 0; last_name = 0; if (gfs) gfs->errors = gfc.errors[1]; if (gfs && gfc.errors[1] == 0 && !(read_flags & GIF_READ_TRAILING_GARBAGE_OK) && !grr->eofer(grr)) gif_read_error(&gfc, 0, "trailing garbage after GIF ignored"); /* finally, export last message */ gif_read_error(&gfc, -1, 0); return gfs; } Gif_Stream * Gif_FullReadFile(FILE *f, int read_flags, const char* landmark, Gif_ReadErrorHandler h) { Gif_Reader grr; if (!f) return 0; grr.f = f; grr.pos = 0; grr.is_record = 0; grr.byte_getter = file_byte_getter; grr.block_getter = file_block_getter; grr.eofer = file_eofer; return read_gif(&grr, read_flags, landmark, h); } Gif_Stream * Gif_FullReadRecord(const Gif_Record *gifrec, int read_flags, const char* landmark, Gif_ReadErrorHandler h) { Gif_Reader grr; if (!gifrec) return 0; make_data_reader(&grr, gifrec->data, gifrec->length); if (read_flags & GIF_READ_CONST_RECORD) read_flags |= GIF_READ_COMPRESSED; return read_gif(&grr, read_flags, landmark, h); } #undef Gif_ReadFile #undef Gif_ReadRecord Gif_Stream * Gif_ReadFile(FILE *f) { return Gif_FullReadFile(f, GIF_READ_UNCOMPRESSED, 0, 0); } Gif_Stream * Gif_ReadRecord(const Gif_Record *gifrec) { return Gif_FullReadRecord(gifrec, GIF_READ_UNCOMPRESSED, 0, 0); } void Gif_SetErrorHandler(Gif_ReadErrorHandler handler) { default_error_handler = handler; } #ifdef __cplusplus } #endif gifsicle-1.96/src/gifsicle.c000066400000000000000000001767241475770763400160230ustar00rootroot00000000000000/* -*- c-basic-offset: 2 -*- */ /* gifsicle.c - gifsicle's main loop. Copyright (C) 1997-2025 Eddie Kohler, ekohler@gmail.com This file is part of gifsicle. Gifsicle is free software. It is distributed under the GNU Public License, version 2; you can copy, distribute, or alter it at will, as long as this notice is kept intact and this source code is made available. There is no warranty, express or implied. */ #include #include "gifsicle.h" #include "kcolor.h" #include #include #include #include #include #include #if HAVE_UNISTD_H # include #endif #ifndef static_assert #define static_assert(x, msg) switch ((int) (x)) case 0: case !!((int) (x)): #endif Gt_Frame def_frame; Gt_Frameset *frames = 0; int first_input_frame = 0; Gt_Frameset *nested_frames = 0; Gif_Stream *input = 0; const char *input_name = 0; static int unoptimizing = 0; const int GIFSICLE_DEFAULT_THREAD_COUNT = 8; int thread_count = 0; static int gif_read_flags = 0; static int nextfile = 0; Gif_CompressInfo gif_write_info; static int frames_done = 0; static int files_given = 0; int warn_local_colormaps = 1; static Gt_ColorTransform *input_transforms; static Gt_ColorTransform *output_transforms; int mode = BLANK_MODE; int nested_mode = 0; static int infoing = 0; int verbosing = 0; static int no_ignore_errors = 0; #define CHANGED(next, flag) (((next) & (1<<(flag))) != 0) #define UNCHECKED_MARK_CH(where, what) \ next_##where |= 1< suppresses normal output, can%,t use with an\n output mode like %<--merge%> or %<--batch%>.\n (Try %<-II%>, which doesn%,t suppress normal output.)"); if (newmode != BLANK_MODE && newmode != mode) fatal_error("too late to change modes"); } void set_frame_change(int kind) { int i; Gt_Frameset *fset; set_mode(BLANK_MODE); if (mode < DELETING && frames_done) { fatal_error("frame selection and frame changes don%,t mix"); return; } assert(!nested_mode); nested_mode = mode; if (frame_spec_1 > frame_spec_2) { i = frame_spec_1; frame_spec_1 = frame_spec_2; frame_spec_2 = i; } switch (kind) { case DELETE_OPT: mode = DELETING; break; case REPLACE_OPT: for (i = frame_spec_1; i < frame_spec_2; i++) FRAME(frames, i).use = 0; /* We want to use the last frame's delay, but nothing else about it. */ FRAME(frames, i).use = -1; /* FALLTHRU */ case INSERT_OPT: /* Define a nested frameset (or use an existing one). */ fset = FRAME(frames, frame_spec_2).nest; if (!fset) fset = new_frameset(8); FRAME(frames, frame_spec_2).nest = fset; /* Later: Merge frames at the end of the nested frameset. */ mode = INSERTING; nested_frames = frames; frames = fset; break; case APPEND_OPT: /* Just merge frames at the end of this frameset. */ mode = INSERTING; break; } } static void frame_change_done(void) { if (nested_mode) mode = nested_mode; if (nested_frames) frames = nested_frames; nested_mode = 0; nested_frames = 0; } static void show_frame(int imagenumber, int usename) { Gif_Image *gfi; Gt_Frame *frame; if (!input || !(gfi = Gif_GetImage(input, imagenumber))) return; switch (mode) { case MERGING: case INSERTING: case EXPLODING: case INFOING: case BATCHING: if (!frames_done) clear_frameset(frames, first_input_frame); frame = add_frame(frames, input, gfi); if (usename) frame->explode_by_name = 1; break; case DELETING: frame = &FRAME(frames, first_input_frame + imagenumber); frame->use = 0; break; } next_frame = 0; frames_done = 1; } /***** * input a stream **/ static void gifread_error(Gif_Stream* gfs, Gif_Image* gfi, int is_error, const char* message) { static int last_is_error = 0; static char last_landmark[256]; static char last_message[256]; static int different_error_count = 0; static int same_error_count = 0; char landmark[256]; int which_image = Gif_ImageNumber(gfs, gfi); if (gfs && which_image < 0) which_image = gfs->nimages; /* ignore warnings if "no_warning" */ if (no_warnings && is_error == 0) return; if (message) { const char *filename = gfs && gfs->landmark ? gfs->landmark : ""; if (gfi && (which_image != 0 || gfs->nimages > 1)) snprintf(landmark, sizeof(landmark), "%s:#%d", filename, which_image < 0 ? gfs->nimages : which_image); else snprintf(landmark, sizeof(landmark), "%s", filename); } if (last_message[0] && different_error_count <= 10 && (!message || strcmp(message, last_message) != 0 || strcmp(landmark, last_landmark) != 0)) { const char* etype = last_is_error ? "read error: " : ""; void (*f)(const char*, const char*, ...) = last_is_error ? lerror : lwarning; if (gfi && gfi->user_flags) /* error already reported */; else if (same_error_count == 1) f(last_landmark, "%s%s", etype, last_message); else if (same_error_count > 0) f(last_landmark, "%s%s (%d times)", etype, last_message, same_error_count); same_error_count = 0; last_message[0] = 0; } if (message) { if (last_message[0] == 0) different_error_count++; same_error_count++; strncpy(last_message, message, sizeof(last_message)); last_message[sizeof(last_message) - 1] = 0; strncpy(last_landmark, landmark, sizeof(last_landmark)); last_landmark[sizeof(last_landmark) - 1] = 0; last_is_error = is_error; if (different_error_count == 11) { if (!(gfi && gfi->user_flags)) error(0, "(plus more errors; is this GIF corrupt?)"); different_error_count++; } } else last_message[0] = 0; { unsigned long missing; if (message && sscanf(message, "missing %lu pixel", &missing) == 1 && missing > 10000 && no_ignore_errors) { gifread_error(gfs, 0, -1, 0); lerror(landmark, "fatal error: too many missing pixels, giving up"); exit(1); } } if (gfi && is_error < 0) gfi->user_flags |= 1; } struct StoredFile { FILE *f; struct StoredFile *next; char name[1]; }; static struct StoredFile *stored_files = 0; static FILE * open_giffile(const char *name) { struct StoredFile *sf; FILE *f; if (name == 0 || strcmp(name, "-") == 0) { #ifndef OUTPUT_GIF_TO_TERMINAL if (isatty(fileno(stdin))) { lerror("", "Is a terminal"); return NULL; } #endif #if defined(_MSDOS) || defined(_WIN32) _setmode(_fileno(stdin), _O_BINARY); #elif defined(__DJGPP__) setmode(fileno(stdin), O_BINARY); #elif defined(__EMX__) _fsetmode(stdin, "b"); #endif return stdin; } if (nextfile) for (sf = stored_files; sf; sf = sf->next) if (strcmp(name, sf->name) == 0) return sf->f; f = fopen(name, "rb"); if (f && nextfile) { sf = (struct StoredFile *) malloc(sizeof(struct StoredFile) + strlen(name)); sf->f = f; sf->next = stored_files; stored_files = sf; strcpy(sf->name, name); } else if (!f) lerror(name, "%s", strerror(errno)); return f; } static void close_giffile(FILE *f, int final) { struct StoredFile **sf_pprev, *sf; if (!final && nextfile) { int c = getc(f); if (c == EOF) final = 1; else ungetc(c, f); } for (sf_pprev = &stored_files; (sf = *sf_pprev); sf_pprev = &sf->next) if (sf->f == f) { if (final) { fclose(f); *sf_pprev = sf->next; free((void *) sf); } return; } if (f != stdin) fclose(f); } void input_stream(const char *name) { char* component_namebuf; FILE *f; Gif_Stream *gfs; int i; int saved_next_frame = next_frame; int componentno = 0; const char *main_name = 0; Gt_Frame old_def_frame; input = 0; input_name = name; frames_done = 0; next_frame = 0; next_input = 0; if (next_output) combine_output_options(); files_given++; set_mode(BLANK_MODE); f = open_giffile(name); if (!f) return; if (f == stdin) { name = ""; input_name = 0; } main_name = name; retry_file: /* change filename for component files */ componentno++; if (componentno > 1) { size_t namelen = strlen(main_name) + 10; component_namebuf = (char*) malloc(namelen); snprintf(component_namebuf, namelen, "%s~%d", main_name, componentno); name = component_namebuf; } else component_namebuf = 0; /* check for empty file */ i = getc(f); if (i == EOF) { if (!(gif_read_flags & GIF_READ_TRAILING_GARBAGE_OK)) lerror(name, "empty file"); else if (nextfile) lerror(name, "no more images in file"); goto error; } ungetc(i, f); if (verbosing) verbose_open('<', name); /* read file */ { int old_error_count = error_count; gfs = Gif_FullReadFile(f, gif_read_flags | GIF_READ_COMPRESSED, name, gifread_error); if ((!gfs || (Gif_ImageCount(gfs) == 0 && gfs->errors > 0)) && componentno != 1) lerror(name, "trailing garbage ignored"); if (!no_ignore_errors) error_count = old_error_count; } if (!gfs || (Gif_ImageCount(gfs) == 0 && gfs->errors > 0)) { if (componentno == 1) lerror(name, "file not in GIF format"); Gif_DeleteStream(gfs); if (verbosing) verbose_close('>'); goto error; } /* special processing for components after the first */ if (componentno > 1) { if (mode == BATCHING || mode == INSERTING) fatal_error("%s: %<--multifile%> is useful only in merge mode", name); input_done(); } input = gfs; /* Processing when we've got a new input frame */ set_mode(BLANK_MODE); if (active_output_data.output_name == 0) { /* Don't override explicit output names. This code works 'cause output_name is reset to 0 after each output. */ if (mode == BATCHING) active_output_data.output_name = input_name; else if (mode == EXPLODING) { /* Explode into current directory. */ const char *explode_name = (input_name ? input_name : "#stdin#"); const char *slash = strrchr(explode_name, PATHNAME_SEPARATOR); if (slash) active_output_data.output_name = slash + 1; else active_output_data.output_name = explode_name; } } /* This code rather sucks. Here's the problem: Since we consider options strictly sequentially, one at a time, we can't tell the difference between these: --name=X g.gif h.gif // name on g.gif #0 --name=X g.gif #2 h.gif // name on g.gif #2 g.gif --name=X #2 h.gif // name on g.gif #2 g.gif --name=X h.gif // name on h.gif #0 !!! Here's the solution. Mark when we CHANGE an option. After processing an input GIF, mark all the options as 'unchanged' -- but leave the VALUES as is. Then when we read the next frame, CLEAR the unchanged options. So it's like so: (* means changed, . means not.) [-.] --name=X [X*] g.gif [X.] #2 [-.] h.gif == name on g.gif #2 [-.] g.gif [-.] --name=X [X*] #2 [-.] h.gif == name on g.gif #2 [-.] --name=X [X*] g.gif [X.|-.] h.gif == name on g.gif #0 [-.] g.gif [-.] --name=X [X*] h.gif == name on h.gif #0 */ /* Clear old options from the last input stream */ if (!CHANGED(saved_next_frame, CH_NAME)) def_frame.name = 0; if (!CHANGED(saved_next_frame, CH_COMMENT)) def_frame.comment = 0; if (!CHANGED(saved_next_frame, CH_EXTENSION)) def_frame.extensions = 0; def_frame.input_filename = input_name; old_def_frame = def_frame; first_input_frame = frames->count; def_frame.position_is_offset = 1; for (i = 0; i < gfs->nimages; i++) add_frame(frames, gfs, gfs->images[i]); def_frame = old_def_frame; if (unoptimizing) if (!Gif_FullUnoptimize(gfs, GIF_UNOPTIMIZE_SIMPLEST_DISPOSAL)) { static int context = 0; if (!context) { lwarning(name, "GIF too complex to unoptimize\n" " (The reason was local color tables or complex transparency.\n" " Try running the GIF through % first.)"); context = 1; } else lwarning(name, "GIF too complex to unoptimize"); } apply_color_transforms(input_transforms, gfs); gfs->refcount++; /* Read more files. */ free(component_namebuf); if ((gif_read_flags & GIF_READ_TRAILING_GARBAGE_OK) && !nextfile) goto retry_file; close_giffile(f, 0); return; error: free(component_namebuf); close_giffile(f, 1); } void input_done(void) { if (!input) return; if (verbosing) verbose_close('>'); Gif_DeleteStream(input); input = 0; if (mode == DELETING) frame_change_done(); if (mode == BATCHING || mode == EXPLODING) output_frames(); } /***** * colormap stuff **/ static void set_new_fixed_colormap(const char *name, int exact) { int i; if (name && strcmp(name, "web") == 0) { Gif_Colormap *cm = Gif_NewFullColormap(216, 256); Gif_Color *col = cm->col; for (i = 0; i < 216; i++) { col[i].gfc_red = (i / 36) * 0x33; col[i].gfc_green = ((i / 6) % 6) * 0x33; col[i].gfc_blue = (i % 6) * 0x33; } def_output_data.colormap_fixed = cm; } else if (name && (strcmp(name, "gray") == 0 || strcmp(name, "grey") == 0)) { Gif_Colormap *cm = Gif_NewFullColormap(256, 256); Gif_Color *col = cm->col; for (i = 0; i < 256; i++) col[i].gfc_red = col[i].gfc_green = col[i].gfc_blue = i; def_output_data.colormap_fixed = cm; } else if (name && strcmp(name, "bw") == 0) { Gif_Colormap *cm = Gif_NewFullColormap(2, 256); cm->col[0].gfc_red = cm->col[0].gfc_green = cm->col[0].gfc_blue = 0; cm->col[1].gfc_red = cm->col[1].gfc_green = cm->col[1].gfc_blue = 255; def_output_data.colormap_fixed = cm; } else def_output_data.colormap_fixed = read_colormap_file(name, 0); def_output_data.colormap_fixed_exact = exact; } static void do_colormap_change(Gif_Stream *gfs) { if (active_output_data.colormap_fixed) colormap_stream(gfs, active_output_data.colormap_fixed, &active_output_data, !active_output_data.colormap_fixed_exact); if (active_output_data.colormap_size > 0) { kchist kch; Gif_Colormap* (*adapt_func)(kchist*, Gt_OutputData*); Gif_Colormap* new_cm; /* set up the histogram */ { uint32_t ntransp; int i, any_locals = 0; for (i = 0; i < gfs->nimages; i++) if (gfs->images[i]->local) any_locals = 1; kchist_make(&kch, gfs, &ntransp); if (kch.n <= active_output_data.colormap_size && !any_locals && !active_output_data.colormap_fixed) { warning(1, "trivial adaptive palette (only %d colors in source)", kch.n); kchist_cleanup(&kch); return; } active_output_data.colormap_needs_transparency = ntransp > 0; } switch (active_output_data.colormap_algorithm) { case COLORMAP_DIVERSITY: adapt_func = &colormap_flat_diversity; break; case COLORMAP_BLEND_DIVERSITY: adapt_func = &colormap_blend_diversity; break; case COLORMAP_MEDIAN_CUT: adapt_func = &colormap_median_cut; break; default: fatal_error("can't happen"); } new_cm = (*adapt_func)(&kch, &active_output_data); colormap_stream(gfs, new_cm, &active_output_data, 1); Gif_DeleteColormap(new_cm); kchist_cleanup(&kch); } } /***** * output GIF images **/ static void write_stream(const char *output_name, Gif_Stream *gfs) { FILE *f; if (output_name) f = fopen(output_name, "wb"); else { #ifndef OUTPUT_GIF_TO_TERMINAL if (isatty(fileno(stdout))) { lerror("", "Is a terminal: try `-o OUTPUTFILE`"); return; } #endif #if defined(_MSDOS) || defined(_WIN32) _setmode(_fileno(stdout), _O_BINARY); #elif defined(__DJGPP__) setmode(fileno(stdout), O_BINARY); #elif defined(__EMX__) _fsetmode(stdout, "b"); #endif f = stdout; output_name = ""; } if (f) { Gif_FullWriteFile(gfs, &gif_write_info, f); fclose(f); any_output_successful = 1; } else lerror(output_name, "%s", strerror(errno)); } static void merge_and_write_frames(const char *outfile, int f1, int f2) { Gif_Stream *out; int compress_immediately; int colormap_change; int huge_stream; assert(!nested_mode); if (verbosing) verbose_open('[', outfile ? outfile : "#stdout#"); active_output_data.active_output_name = outfile; colormap_change = active_output_data.colormap_size > 0 || active_output_data.colormap_fixed; warn_local_colormaps = !colormap_change; if (!(active_output_data.scaling || (active_output_data.optimizing & GT_OPT_MASK) || colormap_change)) compress_immediately = 1; else compress_immediately = active_output_data.conserve_memory; out = merge_frame_interval(frames, f1, f2, &active_output_data, compress_immediately, &huge_stream); if (out) { double w, h; if (active_output_data.scaling == GT_SCALING_SCALE) { w = active_output_data.scale_x * out->screen_width; h = active_output_data.scale_y * out->screen_height; } else { w = active_output_data.resize_width; h = active_output_data.resize_height; } kc_set_gamma(active_output_data.colormap_gamma_type, active_output_data.colormap_gamma); if (active_output_data.scaling != GT_SCALING_NONE) resize_stream(out, w, h, active_output_data.resize_flags, active_output_data.scale_method, active_output_data.scale_colors); if (colormap_change) do_colormap_change(out); if (output_transforms) apply_color_transforms(output_transforms, out); if (active_output_data.optimizing & GT_OPT_MASK) optimize_fragments(out, active_output_data.optimizing, huge_stream); write_stream(outfile, out); Gif_DeleteStream(out); } if (verbosing) verbose_close(']'); active_output_data.active_output_name = 0; } static void output_information(const char *outfile) { FILE *f; int i, j; Gt_Frame *fr; Gif_Stream *gfs; if (infoing == 2) f = stderr; else if (outfile == 0) f = stdout; else { f = fopen(outfile, "w"); if (!f) { lerror(outfile, "%s", strerror(errno)); return; } } for (i = 0; i < frames->count; i++) FRAME(frames, i).stream->user_flags = 97; for (i = 0; i < frames->count; i++) if (FRAME(frames, i).stream->user_flags == 97) { fr = &FRAME(frames, i); gfs = fr->stream; gfs->user_flags = 0; stream_info(f, gfs, fr->input_filename, fr->info_flags); for (j = i; j < frames->count; j++) if (FRAME(frames, j).stream == gfs) { fr = &FRAME(frames, j); image_info(f, gfs, fr->image, fr->info_flags); } } if (f != stderr && f != stdout) fclose(f); } void output_frames(void) { /* Use the current output name, not the stored output name. This supports 'gifsicle a.gif -o xxx'. It's not like any other option, but seems right: it fits the natural order -- input, then output. */ int i; const char *outfile = active_output_data.output_name; active_output_data.output_name = 0; /* Output information only now. */ if (infoing) output_information(outfile); if (infoing != 1 && frames->count > 0) switch (mode) { case MERGING: case BATCHING: case INFOING: merge_and_write_frames(outfile, 0, -1); break; case EXPLODING: { /* Use the current output name for consistency, even though that means we can't explode different frames to different names. Not a big deal anyway; they can always repeat the gif on the cmd line. */ int max_nimages = 0; for (i = 0; i < frames->count; i++) { Gt_Frame *fr = &FRAME(frames, i); if (fr->stream->nimages > max_nimages) max_nimages = fr->stream->nimages; } if (!outfile) /* Watch out! */ outfile = "-"; for (i = 0; i < frames->count; i++) { Gt_Frame *fr = &FRAME(frames, i); int imagenumber = Gif_ImageNumber(fr->stream, fr->image); char *explodename; const char *imagename = 0; if (fr->explode_by_name) imagename = fr->name ? fr->name : fr->image->identifier; explodename = explode_filename(outfile, imagenumber, imagename, max_nimages); merge_and_write_frames(explodename, i, i); } break; } case INSERTING: /* do nothing */ break; } active_next_output = 0; clear_frameset(frames, 0); /* cropping: clear the 'crop->ready' information, which depended on the last input image. */ if (def_frame.crop) def_frame.crop->ready = 0; } /***** * parsing arguments **/ int frame_argument(Clp_Parser *clp, const char *arg) { /* Returns 0 iff you should try a file named 'arg'. */ int val = parse_frame_spec(clp, arg, -1, 0); if (val == -97) return 0; else if (val > 0) { int i, delta = (frame_spec_1 <= frame_spec_2 ? 1 : -1); for (i = frame_spec_1; i != frame_spec_2 + delta; i += delta) show_frame(i, frame_spec_name != 0); if (next_output) combine_output_options(); return 1; } else return 1; } static int handle_extension(Clp_Parser *clp, int is_app) { Gif_Extension *gfex; const char *extension_type = clp->vstr; const char *extension_body = Clp_Shift(clp, 1); if (!extension_body) { Clp_OptionError(clp, "%O requires two arguments"); return 0; } UNCHECKED_MARK_CH(frame, CH_EXTENSION); if (is_app) gfex = Gif_NewExtension(255, extension_type, 11); else if (!isdigit(extension_type[0]) && extension_type[1] == 0) gfex = Gif_NewExtension(extension_type[0], 0, 0); else { long l = strtol(extension_type, (char **)&extension_type, 0); if (*extension_type != 0 || l < 0 || l >= 256) fatal_error("bad extension type: must be a number between 0 and 255"); gfex = Gif_NewExtension(l, 0, 0); } gfex->data = (uint8_t *)extension_body; gfex->length = strlen(extension_body); gfex->next = def_frame.extensions; def_frame.extensions = gfex; return 1; } /***** * option processing **/ static void initialize_def_frame(void) { /* frame defaults */ def_frame.stream = 0; def_frame.image = 0; def_frame.use = 1; def_frame.name = 0; def_frame.no_name = 0; def_frame.comment = 0; def_frame.no_comments = 0; def_frame.interlacing = -1; def_frame.transparent.haspixel = 0; def_frame.left = -1; def_frame.top = -1; def_frame.position_is_offset = 0; def_frame.crop = 0; def_frame.delay = -1; def_frame.disposal = -1; def_frame.nest = 0; def_frame.explode_by_name = 0; def_frame.no_extensions = 0; def_frame.no_app_extensions = 0; def_frame.extensions = 0; def_frame.flip_horizontal = 0; def_frame.flip_vertical = 0; def_frame.total_crop = 0; /* output defaults */ def_output_data.output_name = 0; def_output_data.screen_mode = 0; def_output_data.screen_width = -1; def_output_data.screen_height = -1; def_output_data.background.haspixel = 0; def_output_data.loopcount = -2; def_output_data.colormap_size = 0; def_output_data.colormap_fixed = 0; def_output_data.colormap_fixed_exact = 0; def_output_data.colormap_algorithm = COLORMAP_DIVERSITY; def_output_data.dither_type = dither_none; def_output_data.dither_name = "none"; def_output_data.colormap_gamma_type = KC_GAMMA_SRGB; def_output_data.colormap_gamma = 2.2; def_output_data.optimizing = 0; def_output_data.scaling = GT_SCALING_NONE; def_output_data.scale_method = SCALE_METHOD_MIX; def_output_data.scale_colors = 0; def_output_data.conserve_memory = 0; active_output_data = def_output_data; } static void combine_output_options(void) { int recent = next_output; next_output = active_next_output; #define COMBINE_ONE_OUTPUT_OPTION(value, field) \ if (CHANGED(recent, value)) { \ MARK_CH(output, value); \ active_output_data.field = def_output_data.field; \ } COMBINE_ONE_OUTPUT_OPTION(CH_OUTPUT, output_name); if (CHANGED(recent, CH_LOGICAL_SCREEN)) { MARK_CH(output, CH_LOGICAL_SCREEN); active_output_data.screen_mode = def_output_data.screen_mode; active_output_data.screen_width = def_output_data.screen_width; active_output_data.screen_height = def_output_data.screen_height; } COMBINE_ONE_OUTPUT_OPTION(CH_BACKGROUND, background); COMBINE_ONE_OUTPUT_OPTION(CH_LOOPCOUNT, loopcount); COMBINE_ONE_OUTPUT_OPTION(CH_OPTIMIZE, optimizing); COMBINE_ONE_OUTPUT_OPTION(CH_COLORMAP, colormap_size); COMBINE_ONE_OUTPUT_OPTION(CH_COLORMAP_METHOD, colormap_algorithm); if (CHANGED(recent, CH_USE_COLORMAP)) { MARK_CH(output, CH_USE_COLORMAP); if (def_output_data.colormap_fixed) def_output_data.colormap_fixed->refcount++; Gif_DeleteColormap(active_output_data.colormap_fixed); active_output_data.colormap_fixed = def_output_data.colormap_fixed; active_output_data.colormap_fixed_exact = def_output_data.colormap_fixed_exact; } if (CHANGED(recent, CH_DITHER)) { MARK_CH(output, CH_DITHER); active_output_data.dither_type = def_output_data.dither_type; active_output_data.dither_data = def_output_data.dither_data; } if (CHANGED(recent, CH_GAMMA)) { MARK_CH(output, CH_GAMMA); active_output_data.colormap_gamma_type = def_output_data.colormap_gamma_type; active_output_data.colormap_gamma = def_output_data.colormap_gamma; } if (CHANGED(recent, CH_RESIZE)) { MARK_CH(output, CH_RESIZE); active_output_data.scaling = def_output_data.scaling; active_output_data.resize_width = def_output_data.resize_width; active_output_data.resize_height = def_output_data.resize_height; active_output_data.resize_flags = def_output_data.resize_flags; active_output_data.scale_x = def_output_data.scale_x; active_output_data.scale_y = def_output_data.scale_y; } if (CHANGED(recent, CH_RESIZE_METHOD)) { MARK_CH(output, CH_RESIZE_METHOD); active_output_data.scale_method = def_output_data.scale_method; } if (CHANGED(recent, CH_SCALE_COLORS)) { MARK_CH(output, CH_SCALE_COLORS); active_output_data.scale_colors = def_output_data.scale_colors; } COMBINE_ONE_OUTPUT_OPTION(CH_MEMORY, conserve_memory); def_output_data.colormap_fixed = 0; def_output_data.colormap_fixed_exact = 0; def_output_data.output_name = 0; active_next_output |= next_output; next_output = 0; } static void redundant_option_warning(const char* opttype) { static int context = 0; if (!context) { warning(0, "redundant %s option\n" " (The %s option was overridden by another %s option\n" " before it had any effect.)", opttype, opttype, opttype); context = 1; } else warning(0, "redundant %s option", opttype); } static void print_useless_options(const char *type_name, int value, const char *names[]) { int explanation_printed = 0; int i; if (!value) return; for (i = 0; i < 32; i++) if (CHANGED(value, i)) { if (!explanation_printed) { warning(0, "useless %s-related %s option\n" " (It didn%,t affect any %s.)", names[i], type_name, type_name); explanation_printed = 1; } else warning(0, "useless %s-related %s option", names[i], type_name); } } static Gt_Crop * copy_crop(Gt_Crop *oc) { Gt_Crop *nc = Gif_New(Gt_Crop); /* Memory leak on crops, but this just is NOT a problem. */ if (oc) *nc = *oc; else memset(nc, 0, sizeof(Gt_Crop)); nc->ready = 0; return nc; } static void parse_resize_geometry_opt(Gt_OutputData* odata, const char* str, Clp_Parser* clp) { double x, y; int flags = GT_RESIZE_FIT, scale = 0; if (*str == '_' || *str == 'x') { x = 0; str += (*str == '_'); } else if (isdigit((unsigned char) *str)) x = strtol(str, (char**) &str, 10); else goto error; if (*str == 'x') { ++str; if (*str == '_' || !isdigit((unsigned char) *str)) { y = 0; str += (*str == '_'); } else y = strtol(str, (char**) &str, 10); } else y = x; for (; *str != 0; ++str) if (*str == '%') scale = 1; else if (*str == '!') flags = 0; else if (*str == '^') flags |= GT_RESIZE_FIT | GT_RESIZE_MIN_DIMEN; else if (*str == '<') flags |= GT_RESIZE_FIT | GT_RESIZE_FIT_UP; else if (*str == '>') flags |= GT_RESIZE_FIT | GT_RESIZE_FIT_DOWN; else goto error; if (scale) { odata->scaling = GT_SCALING_SCALE; odata->scale_x = x / 100.0; odata->scale_y = y / 100.0; } else { odata->scaling = GT_SCALING_RESIZE; odata->resize_width = x; odata->resize_height = y; } odata->resize_flags = flags; return; error: Clp_OptionError(clp, "argument to %O must be a valid geometry specification"); } /***** * main **/ int main(int argc, char *argv[]) { /* Check SIZEOF constants (useful for Windows). If these assertions fail, you've used the wrong Makefile. You should've used Makefile.w32 for 32-bit Windows and Makefile.w64 for 64-bit Windows. */ static_assert(sizeof(unsigned int) == SIZEOF_UNSIGNED_INT, "unsigned int has the wrong size."); static_assert(sizeof(unsigned long) == SIZEOF_UNSIGNED_LONG, "unsigned long has the wrong size."); static_assert(sizeof(void*) == SIZEOF_VOID_P, "void* has the wrong size."); clp = Clp_NewParser(argc, (const char * const *)argv, sizeof(options) / sizeof(options[0]), options); Clp_AddStringListType (clp, LOOP_TYPE, Clp_AllowNumbers, "infinite", 0, "forever", 0, (const char*) 0); Clp_AddStringListType (clp, DISPOSAL_TYPE, Clp_AllowNumbers, "none", GIF_DISPOSAL_NONE, "asis", GIF_DISPOSAL_ASIS, "background", GIF_DISPOSAL_BACKGROUND, "bg", GIF_DISPOSAL_BACKGROUND, "previous", GIF_DISPOSAL_PREVIOUS, (const char*) 0); Clp_AddStringListType (clp, COLORMAP_ALG_TYPE, 0, "diversity", COLORMAP_DIVERSITY, "blend-diversity", COLORMAP_BLEND_DIVERSITY, "median-cut", COLORMAP_MEDIAN_CUT, (const char*) 0); Clp_AddStringListType (clp, OPTIMIZE_TYPE, Clp_AllowNumbers, "keep-empty", GT_OPT_KEEPEMPTY + 1, "no-keep-empty", GT_OPT_KEEPEMPTY, "drop-empty", GT_OPT_KEEPEMPTY, "no-drop-empty", GT_OPT_KEEPEMPTY + 1, (const char*) 0); Clp_AddStringListType (clp, RESIZE_METHOD_TYPE, 0, "point", SCALE_METHOD_POINT, "sample", SCALE_METHOD_POINT, "mix", SCALE_METHOD_MIX, "box", SCALE_METHOD_BOX, "catrom", SCALE_METHOD_CATROM, "lanczos", SCALE_METHOD_LANCZOS3, "lanczos2", SCALE_METHOD_LANCZOS2, "lanczos3", SCALE_METHOD_LANCZOS3, "mitchell", SCALE_METHOD_MITCHELL, "fast", SCALE_METHOD_POINT, "good", SCALE_METHOD_MIX, (const char*) 0); Clp_AddType(clp, DIMENSIONS_TYPE, 0, parse_dimensions, 0); Clp_AddType(clp, POSITION_TYPE, 0, parse_position, 0); Clp_AddType(clp, SCALE_FACTOR_TYPE, 0, parse_scale_factor, 0); Clp_AddType(clp, FRAME_SPEC_TYPE, 0, parse_frame_spec, 0); Clp_AddType(clp, COLOR_TYPE, Clp_DisallowOptions, parse_color, 0); Clp_AddType(clp, RECTANGLE_TYPE, 0, parse_rectangle, 0); Clp_AddType(clp, TWO_COLORS_TYPE, Clp_DisallowOptions, parse_two_colors, 0); Clp_SetOptionChar(clp, '+', Clp_ShortNegated); Clp_SetErrorHandler(clp, clp_error_handler); program_name = Clp_ProgramName(clp); frames = new_frameset(16); initialize_def_frame(); Gif_InitCompressInfo(&gif_write_info); Gif_SetErrorHandler(gifread_error); #if ENABLE_THREADS pthread_mutex_init(&kd3_sort_lock, 0); #endif /* Yep, I'm an idiot. GIF dimensions are unsigned 16-bit integers. I assume that these numbers will fit in an 'int'. This assertion tests that assumption. Really I should go through & change everything over, but it doesn't seem worth my time. */ { uint16_t m = 0xFFFFU; int i = m; assert(i > 0 && "configuration/lameness failure! bug the author!"); } while (1) { int opt = Clp_Next(clp); switch (opt) { /* MODE OPTIONS */ case 'b': set_mode(BATCHING); break; case 'm': set_mode(MERGING); break; case 'e': set_mode(EXPLODING); def_frame.explode_by_name = 0; break; case 'E': set_mode(EXPLODING); def_frame.explode_by_name = 1; break; /* INFORMATION OPTIONS */ case INFO_OPT: if (clp->negated) infoing = 0; else /* switch between infoing == 1 (suppress regular output) and 2 (don't suppress) */ infoing = (infoing == 1 ? 2 : 1); break; case COLOR_INFO_OPT: if (clp->negated) def_frame.info_flags &= ~INFO_COLORMAPS; else { def_frame.info_flags |= INFO_COLORMAPS; if (!infoing) infoing = 1; } break; case EXTENSION_INFO_OPT: if (clp->negated) def_frame.info_flags &= ~INFO_EXTENSIONS; else { def_frame.info_flags |= INFO_EXTENSIONS; if (!infoing) infoing = 1; } break; case SIZE_INFO_OPT: if (clp->negated) def_frame.info_flags &= ~INFO_SIZES; else { def_frame.info_flags |= INFO_SIZES; if (!infoing) infoing = 1; } break; case VERBOSE_OPT: verbosing = clp->negated ? 0 : 1; break; /* FRAME CHANGE OPTIONS */ case DELETE_OPT: case REPLACE_OPT: case INSERT_OPT: case APPEND_OPT: frame_change_done(); set_frame_change(opt); break; case ALTER_DONE_OPT: frame_change_done(); break; /* IMAGE OPTIONS */ case NAME_OPT: if (clp->negated) goto no_names; MARK_CH(frame, CH_NAME); def_frame.name = clp->vstr; break; no_names: case NO_NAME_OPT: MARK_CH(frame, CH_NAME); def_frame.no_name = 1; def_frame.name = 0; break; case SAME_NAME_OPT: def_frame.no_name = 0; def_frame.name = 0; break; case COMMENT_OPT: if (clp->negated) goto no_comments; MARK_CH(frame, CH_COMMENT); if (!def_frame.comment) def_frame.comment = Gif_NewComment(); Gif_AddComment(def_frame.comment, clp->vstr, -1); break; no_comments: case NO_COMMENTS_OPT: Gif_DeleteComment(def_frame.comment); def_frame.comment = 0; def_frame.no_comments = 1; break; case SAME_COMMENTS_OPT: def_frame.no_comments = 0; break; case 'i': MARK_CH(frame, CH_INTERLACE); def_frame.interlacing = clp->negated ? 0 : 1; break; case SAME_INTERLACE_OPT: def_frame.interlacing = -1; break; case POSITION_OPT: MARK_CH(frame, CH_POSITION); def_frame.left = clp->negated ? 0 : position_x; def_frame.top = clp->negated ? 0 : position_y; break; case SAME_POSITION_OPT: def_frame.left = -1; def_frame.top = -1; break; case 't': MARK_CH(frame, CH_TRANSPARENT); if (clp->negated) def_frame.transparent.haspixel = 255; else { def_frame.transparent = parsed_color; def_frame.transparent.haspixel = parsed_color.haspixel ? 2 : 1; } break; case SAME_TRANSPARENT_OPT: def_frame.transparent.haspixel = 0; break; case BACKGROUND_OPT: MARK_CH(output, CH_BACKGROUND); if (clp->negated) { def_output_data.background.haspixel = 2; def_output_data.background.pixel = 0; } else { def_output_data.background = parsed_color; def_output_data.background.haspixel = parsed_color.haspixel ? 2 : 1; } break; case SAME_BACKGROUND_OPT: MARK_CH(output, CH_BACKGROUND); def_output_data.background.haspixel = 0; break; case LOGICAL_SCREEN_OPT: MARK_CH(output, CH_LOGICAL_SCREEN); if (clp->negated) { def_output_data.screen_mode = -1; def_output_data.screen_width = def_output_data.screen_height = 0; } else { def_output_data.screen_mode = 1; def_output_data.screen_width = dimensions_x; def_output_data.screen_height = dimensions_y; } break; case SAME_LOGICAL_SCREEN_OPT: MARK_CH(output, CH_LOGICAL_SCREEN); def_output_data.screen_mode = 0; def_output_data.screen_width = def_output_data.screen_height = -1; break; case CROP_OPT: if (clp->negated) goto no_crop; MARK_CH(frame, CH_CROP); { Gt_Crop *crop = copy_crop(def_frame.crop); /* Memory leak on crops, but this just is NOT a problem. */ crop->spec_x = position_x; crop->spec_y = position_y; crop->spec_w = dimensions_x; crop->spec_h = dimensions_y; def_frame.crop = crop; } break; no_crop: case SAME_CROP_OPT: def_frame.crop = 0; break; case CROP_TRANSPARENCY_OPT: if (clp->negated) goto no_crop_transparency; def_frame.crop = copy_crop(def_frame.crop); def_frame.crop->transparent_edges = 1; break; no_crop_transparency: if (def_frame.crop && def_frame.crop->transparent_edges) { def_frame.crop = copy_crop(def_frame.crop); def_frame.crop->transparent_edges = 0; } break; /* extensions options */ case NO_EXTENSIONS_OPT: def_frame.no_extensions = 1; break; case NO_APP_EXTENSIONS_OPT: def_frame.no_app_extensions = 1; break; case SAME_EXTENSIONS_OPT: def_frame.no_extensions = 0; break; case SAME_APP_EXTENSIONS_OPT: def_frame.no_app_extensions = 0; break; case EXTENSION_OPT: if (!handle_extension(clp, 0)) goto bad_option; break; case APP_EXTENSION_OPT: if (!handle_extension(clp, 1)) goto bad_option; break; /* IMAGE DATA OPTIONS */ case FLIP_HORIZ_OPT: MARK_CH(frame, CH_FLIP); def_frame.flip_horizontal = !clp->negated; break; case FLIP_VERT_OPT: MARK_CH(frame, CH_FLIP); def_frame.flip_vertical = !clp->negated; break; case NO_FLIP_OPT: def_frame.flip_horizontal = def_frame.flip_vertical = 0; break; case NO_ROTATE_OPT: def_frame.rotation = 0; break; case ROTATE_90_OPT: MARK_CH(frame, CH_ROTATE); def_frame.rotation = 1; break; case ROTATE_180_OPT: MARK_CH(frame, CH_ROTATE); def_frame.rotation = 2; break; case ROTATE_270_OPT: MARK_CH(frame, CH_ROTATE); def_frame.rotation = 3; break; /* ANIMATION OPTIONS */ case 'd': MARK_CH(frame, CH_DELAY); def_frame.delay = clp->negated ? 0 : clp->val.i; break; case SAME_DELAY_OPT: def_frame.delay = -1; break; case DISPOSAL_OPT: MARK_CH(frame, CH_DISPOSAL); if (clp->negated) def_frame.disposal = GIF_DISPOSAL_NONE; else if (clp->val.i < 0 || clp->val.i > 7) error(0, "disposal must be between 0 and 7"); else def_frame.disposal = clp->val.i; break; case SAME_DISPOSAL_OPT: def_frame.disposal = -1; break; case 'l': MARK_CH(output, CH_LOOPCOUNT); if (clp->negated) def_output_data.loopcount = -1; else def_output_data.loopcount = (clp->have_val ? clp->val.i : 0); break; case SAME_LOOPCOUNT_OPT: MARK_CH(output, CH_LOOPCOUNT); def_output_data.loopcount = -2; break; case OPTIMIZE_OPT: { int o; UNCHECKED_MARK_CH(output, CH_OPTIMIZE); if (clp->negated || (clp->have_val && clp->val.i < 0)) o = 0; else o = (clp->have_val ? clp->val.i : 1); if (o > GT_OPT_MASK && (o & 1)) def_output_data.optimizing |= o - 1; else if (o > GT_OPT_MASK) def_output_data.optimizing &= ~o; else def_output_data.optimizing = (def_output_data.optimizing & ~GT_OPT_MASK) | o; break; } case UNOPTIMIZE_OPT: UNCHECKED_MARK_CH(input, CH_UNOPTIMIZE); unoptimizing = clp->negated ? 0 : 1; break; case THREADS_OPT: if (clp->negated) thread_count = 0; else if (clp->have_val) thread_count = clp->val.i; else thread_count = GIFSICLE_DEFAULT_THREAD_COUNT; break; /* WHOLE-GIF OPTIONS */ case CAREFUL_OPT: { if (clp->negated) gif_write_info.flags = 0; else gif_write_info.flags = GIF_WRITE_CAREFUL_MIN_CODE_SIZE | GIF_WRITE_EAGER_CLEAR; break; } case CHANGE_COLOR_OPT: { next_input |= 1 << CH_CHANGE_COLOR; if (clp->negated) input_transforms = delete_color_transforms (input_transforms, &color_change_transformer); else if (parsed_color2.haspixel) error(0, "COLOR2 must be in RGB format in %<--change-color COLOR1 COLOR2%>"); else input_transforms = append_color_change (input_transforms, parsed_color, parsed_color2); break; } case COLOR_TRANSFORM_OPT: next_output |= 1 << CH_COLOR_TRANSFORM; if (clp->negated) output_transforms = delete_color_transforms (output_transforms, &pipe_color_transformer); else output_transforms = append_color_transform (output_transforms, &pipe_color_transformer, (void *)clp->vstr); break; case COLORMAP_OPT: MARK_CH(output, CH_COLORMAP); if (clp->negated) def_output_data.colormap_size = 0; else { def_output_data.colormap_size = clp->val.i; if (def_output_data.colormap_size < 2 || def_output_data.colormap_size > 256) { Clp_OptionError(clp, "argument to %O must be between 2 and 256"); def_output_data.colormap_size = 0; } } break; case GRAY_OPT: MARK_CH(output, CH_USE_COLORMAP); Gif_DeleteColormap(def_output_data.colormap_fixed); set_new_fixed_colormap("gray", 0); break; case USE_COLORMAP_OPT: MARK_CH(output, CH_USE_COLORMAP); Gif_DeleteColormap(def_output_data.colormap_fixed); if (clp->negated) def_output_data.colormap_fixed = 0; else set_new_fixed_colormap(clp->vstr, 0); break; case USE_EXACT_COLORMAP_OPT: MARK_CH(output, CH_USE_COLORMAP); Gif_DeleteColormap(def_output_data.colormap_fixed); if (clp->negated) def_output_data.colormap_fixed = 0; else set_new_fixed_colormap(clp->vstr, 1); break; case COLORMAP_ALGORITHM_OPT: MARK_CH(output, CH_COLORMAP_METHOD); def_output_data.colormap_algorithm = clp->val.i; break; case DITHER_OPT: { const char* name; if (clp->negated) name = "none"; else if (!clp->have_val) name = "default"; else name = clp->val.s; if (strcmp(name, "posterize") == 0) name = "none"; if (strcmp(name, def_output_data.dither_name) != 0 && (strcmp(name, "none") == 0 || strcmp(def_output_data.dither_name, "default") != 0)) MARK_CH(output, CH_DITHER); UNCHECKED_MARK_CH(output, CH_DITHER); if (set_dither_type(&def_output_data, name) < 0) Clp_OptionError(clp, "%<%s%> is not a valid dither", name); def_output_data.dither_name = name; break; } case GAMMA_OPT: { if (clp->negated) { MARK_CH(output, CH_GAMMA); def_output_data.colormap_gamma_type = KC_GAMMA_NUMERIC; def_output_data.colormap_gamma = 1.0; break; } if (strlen(clp->val.s) == 4 && tolower((unsigned char) clp->val.s[0]) == 's' && tolower((unsigned char) clp->val.s[1]) == 'r' && tolower((unsigned char) clp->val.s[2]) == 'g' && tolower((unsigned char) clp->val.s[3]) == 'b') { MARK_CH(output, CH_GAMMA); def_output_data.colormap_gamma_type = KC_GAMMA_SRGB; break; } #if HAVE_CBRTF if (strlen(clp->val.s) == 5 && tolower((unsigned char) clp->val.s[0]) == 'o' && tolower((unsigned char) clp->val.s[1]) == 'k' && tolower((unsigned char) clp->val.s[2]) == 'l' && tolower((unsigned char) clp->val.s[3]) == 'a' && tolower((unsigned char) clp->val.s[4]) == 'b') { MARK_CH(output, CH_GAMMA); def_output_data.colormap_gamma_type = KC_GAMMA_OKLAB; break; } #endif #if HAVE_POW { char* ends; double gamma = strtod(clp->val.s, &ends); if (*clp->val.s && !*ends && !isspace((unsigned char) *clp->val.s)) { MARK_CH(output, CH_GAMMA); def_output_data.colormap_gamma_type = KC_GAMMA_NUMERIC; def_output_data.colormap_gamma = gamma; break; } } #endif Clp_OptionError(clp, "%O not supported"); break; } case RESIZE_OPT: case RESIZE_FIT_OPT: case RESIZE_TOUCH_OPT: MARK_CH(output, CH_RESIZE); if (clp->negated) def_output_data.scaling = GT_SCALING_NONE; else if (dimensions_x <= 0 && dimensions_y <= 0) { error(0, "one of W and H must be positive in %<%s WxH%>", Clp_CurOptionName(clp)); def_output_data.scaling = GT_SCALING_NONE; } else { def_output_data.scaling = GT_SCALING_RESIZE; def_output_data.resize_width = dimensions_x; def_output_data.resize_height = dimensions_y; def_output_data.resize_flags = 0; if (opt != RESIZE_OPT) def_output_data.resize_flags |= GT_RESIZE_FIT; if (opt == RESIZE_FIT_OPT) def_output_data.resize_flags |= GT_RESIZE_FIT_DOWN; } break; case RESIZE_WIDTH_OPT: case RESIZE_HEIGHT_OPT: case RESIZE_FIT_WIDTH_OPT: case RESIZE_FIT_HEIGHT_OPT: case RESIZE_TOUCH_WIDTH_OPT: case RESIZE_TOUCH_HEIGHT_OPT: MARK_CH(output, CH_RESIZE); if (clp->negated) def_output_data.scaling = GT_SCALING_NONE; else if (clp->val.u == 0) { error(0, "%s argument must be positive", Clp_CurOptionName(clp)); def_output_data.scaling = GT_SCALING_NONE; } else { unsigned dimen[2] = {0, 0}; dimen[(opt == RESIZE_HEIGHT_OPT || opt == RESIZE_FIT_HEIGHT_OPT)] = clp->val.u; def_output_data.scaling = GT_SCALING_RESIZE; def_output_data.resize_width = dimen[0]; def_output_data.resize_height = dimen[1]; def_output_data.resize_flags = 0; if (opt != RESIZE_WIDTH_OPT && opt != RESIZE_HEIGHT_OPT) def_output_data.resize_flags |= GT_RESIZE_FIT; if (opt == RESIZE_FIT_WIDTH_OPT || opt == RESIZE_FIT_HEIGHT_OPT) def_output_data.resize_flags |= GT_RESIZE_FIT_DOWN; } break; case SCALE_OPT: MARK_CH(output, CH_RESIZE); if (clp->negated) def_output_data.scaling = GT_SCALING_NONE; else if (parsed_scale_factor_x <= 0 || parsed_scale_factor_y <= 0) { error(0, "%s X and Y factors must be positive", Clp_CurOptionName(clp)); def_output_data.scaling = GT_SCALING_NONE; } else { def_output_data.scaling = GT_SCALING_SCALE; def_output_data.scale_x = parsed_scale_factor_x; def_output_data.scale_y = parsed_scale_factor_y; def_output_data.resize_flags = 0; } break; case RESIZE_GEOMETRY_OPT: MARK_CH(output, CH_RESIZE); if (clp->negated) def_output_data.scaling = GT_SCALING_NONE; else parse_resize_geometry_opt(&def_output_data, clp->val.s, clp); break; case RESIZE_METHOD_OPT: MARK_CH(output, CH_RESIZE_METHOD); def_output_data.scale_method = clp->val.i; break; case RESIZE_COLORS_OPT: MARK_CH(output, CH_SCALE_COLORS); def_output_data.scale_colors = clp->negated ? 0 : clp->val.i; if (def_output_data.scale_colors > 256) { error(0, "%s can be at most 256", Clp_CurOptionName(clp)); def_output_data.scale_colors = 256; } break; case LOSSY_OPT: if (clp->have_val) gif_write_info.loss = clp->val.i; else gif_write_info.loss = 20; break; /* RANDOM OPTIONS */ case NO_WARNINGS_OPT: no_warnings = !clp->negated; break; case WARNINGS_OPT: no_warnings = clp->negated; break; case IGNORE_ERRORS_OPT: no_ignore_errors = clp->negated; break; case CONSERVE_MEMORY_OPT: MARK_CH(output, CH_MEMORY); def_output_data.conserve_memory = clp->negated ? -1 : 1; break; case MULTIFILE_OPT: if (clp->negated) gif_read_flags &= ~GIF_READ_TRAILING_GARBAGE_OK; else { gif_read_flags |= GIF_READ_TRAILING_GARBAGE_OK; nextfile = 0; } break; case NEXTFILE_OPT: if (clp->negated) gif_read_flags &= ~GIF_READ_TRAILING_GARBAGE_OK; else { gif_read_flags |= GIF_READ_TRAILING_GARBAGE_OK; nextfile = 1; } break; case VERSION_OPT: #ifdef GIF_UNGIF printf("LCDF Gifsicle %s (ungif)\n", VERSION); #else printf("LCDF Gifsicle %s\n", VERSION); #endif printf("Copyright (C) 1997-2025 Eddie Kohler\n\ This is free software; see the source for copying conditions.\n\ There is NO warranty, not even for merchantability or fitness for a\n\ particular purpose.\n"); exit(EXIT_OK); break; case HELP_OPT: usage(clp); exit(EXIT_OK); break; case OUTPUT_OPT: MARK_CH(output, CH_OUTPUT); if (strcmp(clp->vstr, "-") == 0) def_output_data.output_name = 0; else def_output_data.output_name = clp->vstr; break; /* NONOPTIONS */ case Clp_NotOption: if (clp->vstr[0] != '#' || !frame_argument(clp, clp->vstr)) { input_done(); input_stream(clp->vstr); } break; case Clp_Done: goto done; bad_option: case Clp_BadOption: short_usage(clp); exit(EXIT_USER_ERR); break; default: break; } } done: if (next_output) combine_output_options(); if (!files_given) input_stream(0); frame_change_done(); input_done(); if ((mode == MERGING && !error_count) || mode == INFOING) output_frames(); verbose_endline(); print_useless_options("frame", next_frame, frame_option_types); print_useless_options("input", next_input, input_option_types); if (any_output_successful) print_useless_options("output", active_next_output, output_option_types); blank_frameset(frames, 0, 0, 1); Clp_DeleteParser(clp); return (error_count ? EXIT_ERR : EXIT_OK); } gifsicle-1.96/src/gifsicle.h000066400000000000000000000220771475770763400160170ustar00rootroot00000000000000/* gifsicle.h - Function declarations for gifsicle. Copyright (C) 1997-2025 Eddie Kohler, ekohler@gmail.com This file is part of gifsicle. Gifsicle is free software. It is distributed under the GNU Public License, version 2; you can copy, distribute, or alter it at will, as long as this notice is kept intact and this source code is made available. There is no warranty, express or implied. */ #ifndef GIFSICLE_H #define GIFSICLE_H #include #include #ifdef __GNUC__ #define NORETURN __attribute__ ((noreturn)) #define USED_ATTR __attribute__ ((used)) #else #define NORETURN #define USED_ATTR #endif typedef struct Gt_Frameset Gt_Frameset; typedef struct Gt_Crop Gt_Crop; typedef struct Gt_ColorTransform Gt_ColorTransform; #if ENABLE_THREADS #include extern pthread_mutex_t kd3_sort_lock; #endif typedef struct Gt_Frame { Gif_Stream *stream; Gif_Image *image; int use; const char *name; int no_name; Gif_Comment *comment; int no_comments; Gif_Color transparent; /* also background */ int interlacing; int left; int top; Gt_Crop *crop; int left_offset; int top_offset; int delay; int disposal; Gt_Frameset *nest; int explode_by_name; int no_extensions; int no_app_extensions; Gif_Extension *extensions; unsigned flip_horizontal: 1; unsigned flip_vertical: 1; unsigned info_flags: 3; unsigned position_is_offset: 1; unsigned total_crop: 1; unsigned rotation; const char *input_filename; } Gt_Frame; struct Gt_Frameset { int count; int cap; Gt_Frame *f; }; struct Gt_Crop { int ready; int transparent_edges; int spec_x; int spec_y; int spec_w; int spec_h; int x; int y; int w; int h; int left_offset; int top_offset; }; typedef void (*colormap_transform_func)(Gif_Colormap *, void *); struct Gt_ColorTransform { Gt_ColorTransform *prev; Gt_ColorTransform *next; colormap_transform_func func; void *data; }; typedef struct { const char *output_name; const char *active_output_name; int screen_mode; int screen_width; int screen_height; Gif_Color background; int loopcount; int colormap_size; Gif_Colormap *colormap_fixed; int colormap_fixed_exact; int colormap_algorithm; int colormap_needs_transparency; int dither_type; const uint8_t* dither_data; const char* dither_name; int colormap_gamma_type; double colormap_gamma; int optimizing; int scaling; int resize_width; int resize_height; int resize_flags; double scale_x; double scale_y; int scale_method; int scale_colors; int conserve_memory; } Gt_OutputData; extern Gt_OutputData active_output_data; extern Clp_Parser* clp; #define GT_SCALING_NONE 0 #define GT_SCALING_RESIZE 1 #define GT_SCALING_SCALE 2 #define GT_RESIZE_FIT 1 #define GT_RESIZE_FIT_DOWN 2 #define GT_RESIZE_FIT_UP 4 #define GT_RESIZE_MIN_DIMEN 8 #define SCALE_METHOD_POINT 0 #define SCALE_METHOD_BOX 1 #define SCALE_METHOD_MIX 2 #define SCALE_METHOD_CATROM 3 #define SCALE_METHOD_LANCZOS2 4 #define SCALE_METHOD_LANCZOS3 5 #define SCALE_METHOD_MITCHELL 6 #define GT_OPT_MASK 0xFFFF #define GT_OPT_KEEPEMPTY 0x10000 /***** * helper **/ static inline int constrain(int low, int x, int high) { return x < low ? low : (x < high ? x : high); } /***** * error & verbose **/ extern const char *program_name; extern int verbosing; extern int error_count; extern int no_warnings; extern int thread_count; extern Gif_CompressInfo gif_write_info; void fatal_error(const char* format, ...) NORETURN; void warning(int need_file, const char* format, ...); void lwarning(const char* landmark, const char* format, ...); void error(int need_file, const char* format, ...); void lerror(const char* landmark, const char* format, ...); void clp_error_handler(Clp_Parser *clp, const char *clp_message); void usage(Clp_Parser* clp); void short_usage(Clp_Parser* clp); void verbose_open(char, const char *); void verbose_close(char); void verbose_endline(void); const char* debug_color_str(const Gif_Color* gfc); #define EXIT_OK 0 #define EXIT_ERR 1 #define EXIT_USER_ERR 1 /***** * info &c **/ #define INFO_COLORMAPS 1 #define INFO_EXTENSIONS 2 #define INFO_SIZES 4 void stream_info(FILE *f, Gif_Stream *gfs, const char *filename, int flags); void image_info(FILE *f, Gif_Stream *gfs, Gif_Image *gfi, int flags); char *explode_filename(const char *filename, int number, const char *name, int max_nimg); /***** * merging images **/ void unmark_colors(Gif_Colormap *); void unmark_colors_2(Gif_Colormap *); void mark_used_colors(Gif_Stream *gfs, Gif_Image *gfi, Gt_Crop *crop, int compress_immediately); int find_color_index(Gif_Color *c, int nc, Gif_Color *); int merge_colormap_if_possible(Gif_Colormap *, Gif_Colormap *); extern int warn_local_colormaps; void merge_stream(Gif_Stream *dest, Gif_Stream *src, int no_comments); void merge_comments(Gif_Comment *destc, Gif_Comment *srcc); Gif_Image* merge_image(Gif_Stream* dest, Gif_Stream* src, Gif_Image* srci, Gt_Frame* srcfr, int same_compressed_ok); void optimize_fragments(Gif_Stream *, int optimizeness, int huge_stream); /***** * image/colormap transformations **/ Gif_Colormap *read_colormap_file(const char *, FILE *); void apply_color_transforms(Gt_ColorTransform *, Gif_Stream *); typedef void (*color_transform_func)(Gif_Colormap *, void *); Gt_ColorTransform *append_color_transform (Gt_ColorTransform *list, color_transform_func, void *); Gt_ColorTransform *delete_color_transforms (Gt_ColorTransform *list, color_transform_func); void color_change_transformer(Gif_Colormap *, void *); Gt_ColorTransform *append_color_change (Gt_ColorTransform *list, Gif_Color, Gif_Color); void pipe_color_transformer(Gif_Colormap *, void *); void combine_crop(Gt_Crop *dstcrop, const Gt_Crop *srccrop, const Gif_Image *gfi); int crop_image(Gif_Image* gfi, Gt_Frame* fr, int preserve_total_crop); void flip_image(Gif_Image* gfi, Gt_Frame* fr, int is_vert); void rotate_image(Gif_Image* gfi, Gt_Frame* fr, int rotation); void resize_dimensions(int* w, int* h, double new_width, double new_height, int flags); void resize_stream(Gif_Stream* gfs, double new_width, double new_height, int flags, int method, int scale_colors); /***** * quantization **/ #define KC_GAMMA_SRGB 0 #define KC_GAMMA_NUMERIC 1 #define KC_GAMMA_OKLAB 2 void kc_set_gamma(int type, double gamma); #define COLORMAP_DIVERSITY 0 #define COLORMAP_BLEND_DIVERSITY 1 #define COLORMAP_MEDIAN_CUT 2 enum { dither_none = 0, dither_default, dither_floyd_steinberg, dither_ordered, dither_ordered_new, dither_atkinson }; int set_dither_type(Gt_OutputData* od, const char* name); void colormap_stream(Gif_Stream*, Gif_Colormap*, Gt_OutputData*, int allow_shrink); struct kchist; Gif_Colormap* colormap_blend_diversity(struct kchist* kch, Gt_OutputData* od); Gif_Colormap* colormap_flat_diversity(struct kchist* kch, Gt_OutputData* od); Gif_Colormap* colormap_median_cut(struct kchist* kch, Gt_OutputData* od); /***** * parsing stuff **/ extern int frame_spec_1; extern int frame_spec_2; extern char * frame_spec_name; extern int dimensions_x; extern int dimensions_y; extern int position_x; extern int position_y; extern Gif_Color parsed_color; extern Gif_Color parsed_color2; extern double parsed_scale_factor_x; extern double parsed_scale_factor_y; int parse_frame_spec(Clp_Parser*, const char*, int, void*); int parse_dimensions(Clp_Parser*, const char*, int, void*); int parse_position(Clp_Parser*, const char*, int, void*); int parse_scale_factor(Clp_Parser*, const char*, int, void*); int parse_color(Clp_Parser*, const char*, int, void*); int parse_rectangle(Clp_Parser*, const char*, int, void*); int parse_two_colors(Clp_Parser*, const char*, int, void*); extern Gif_Stream *input; extern const char *input_name; void input_stream(const char*); void input_done(void); void output_frames(void); /***** * stuff with frames **/ extern Gt_Frame def_frame; #define FRAME(fs, i) ((fs)->f[i]) Gt_Frameset * new_frameset(int initial_cap); Gt_Frame* add_frame(Gt_Frameset*, Gif_Stream*, Gif_Image*); void clear_def_frame_once_options(void); Gif_Stream * merge_frame_interval(Gt_Frameset*, int f1, int f2, Gt_OutputData*, int compress, int *huge); void clear_frameset(Gt_Frameset*, int from); void blank_frameset(Gt_Frameset*, int from, int to, int delete_ob); /***** * mode **/ #define BLANK_MODE 0 #define MERGING 1 #define BATCHING 2 #define EXPLODING 3 #define INFOING 4 #define DELETING 5 #define INSERTING 6 extern int mode; extern int nested_mode; #endif gifsicle-1.96/src/giftoc.c000066400000000000000000000111421475770763400154670ustar00rootroot00000000000000#if HAVE_CONFIG_H # include #endif #ifndef PATHNAME_SEPARATOR # define PATHNAME_SEPARATOR '/' #endif #include #include #include #include #include int is_static = 1; int is_const = 1; static void * fmalloc(int size) { void *p = malloc(size); if (!p) { fputs("giftoc: Out of memory!\n", stderr); exit(1); } return p; } void print_reckless(FILE *f, char *gifrecname) { unsigned long size = 0; int c; int lasthex = 0; printf("\n%sGifRecord %s = { (unsigned char *)\"", is_static ? "static " : "", gifrecname); size = 0; c = getc(f); while (c != EOF) { if (size % 60 == 0) printf("\"\n \""); switch (c) { case '\\': fputs("\\\\", stdout); lasthex = 0; break; case '\"': fputs("\\\"", stdout); lasthex = 0; break; case '\b': fputs("\\b", stdout); lasthex = 0; break; case '\r': fputs("\\r", stdout); lasthex = 0; break; case '\n': fputs("\\n", stdout); lasthex = 0; break; case '\f': fputs("\\f", stdout); lasthex = 0; break; case '\t': fputs("\\t", stdout); lasthex = 0; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': if (lasthex) printf("\\%o", c); else putchar(c); break; default: if (isprint(c)) { putchar(c); lasthex = 0; } else { printf("\\%o", c); lasthex = 1; } break; } size++; c = getc(f); } printf("\",\n %lu\n};\n", size); } void print_unreckless(FILE *f, char *gifrecname) { unsigned long size = 0; int c; printf("\nstatic %sunsigned char %s_data[] = {", (is_const ? "const " : ""), gifrecname); size = 0; c = getc(f); while (c != EOF) { if (size % 20 == 0) printf("\n"); printf("%d,", c); size++; c = getc(f); } printf("};\n%s%sGif_Record %s = { %s_data, %lu };\n", (is_static ? "static " : ""), (is_const ? "const " : ""), gifrecname, gifrecname, size); } int main(int argc, char *argv[]) { int reckless = 0; int make_name = 0; const char *directory = ""; argc--, argv++; while (argv[0] && argv[0][0] == '-') { if (!strcmp(argv[0], "-reckless")) reckless = 1, argc--, argv++; else if (!strcmp(argv[0], "-static")) is_static = 1, argc--, argv++; else if (!strcmp(argv[0], "-extern")) is_static = 0, argc--, argv++; else if (!strcmp(argv[0], "-makename")) make_name = 1, argc--, argv++; else if (!strcmp(argv[0], "-nonconst")) is_const = 0, argc--, argv++; else if (!strcmp(argv[0], "-const")) is_const = 1, argc--, argv++; else if (!strcmp(argv[0], "-dir") && argc > 1) { directory = argv[1], argc -= 2, argv += 2; /* make sure directory is slash-terminated */ if (directory[ strlen(directory) - 1 ] != PATHNAME_SEPARATOR && directory[0]) { size_t len = strlen(directory) + 2; char *ndirectory = (char *)fmalloc(len); snprintf(ndirectory, len, "%s%c", directory, PATHNAME_SEPARATOR); directory = ndirectory; } } else break; } if ((!make_name && argc % 2 != 0) || argc < 1 || (argv[0] && argv[0][0] == '-')) { fprintf(stderr, "\ usage: giftoc [OPTIONS] FILE NAME [FILE NAME...]\n\ or: giftoc -makename [OPTIONS] FILE [FILE...]\n\ OPTIONS are -reckless, -extern, -nonconst, -dir DIR\n"); exit(1); } if (!is_static) printf("#include \"config.h\"\n#include \n\n"); for (; argc > 0; argc--, argv++) { FILE *f; char *rec_name = 0; char *file_name = (char *)fmalloc(strlen(argv[0]) + strlen(directory) + 1); strcpy(file_name, directory); strcat(file_name, argv[0]); f = fopen(file_name, "rb"); if (!f) { fprintf(stderr, "%s: %s\n", file_name, strerror(errno)); goto done; } if (make_name) { char *sin, *sout; sin = strrchr(file_name, PATHNAME_SEPARATOR) + 1; if (!sin) sin = file_name; sout = rec_name = (char *)fmalloc(strlen(sin) + 2); if (isdigit(*sin)) *sout++ = 'N'; for (; *sin; sin++, sout++) if (isalnum(*sin)) *sout = *sin; else *sout = '_'; *sout = 0; } else { argv++, argc--; rec_name = argv[0]; } if (reckless) print_reckless(f, rec_name); else print_unreckless(f, rec_name); done: if (make_name) free(rec_name); free(file_name); if (f) fclose(f); } exit(0); } gifsicle-1.96/src/gifunopt.c000066400000000000000000000144211475770763400160520ustar00rootroot00000000000000/* gifunopt.c - Unoptimization function for the GIF library. Copyright (C) 1997-2025 Eddie Kohler, ekohler@gmail.com This file is part of the LCDF GIF library. The LCDF GIF library is free software. It is distributed under the GNU General Public License, version 2; you can copy, distribute, or alter it at will, as long as this notice is kept intact and this source code is made available. There is no warranty, express or implied. */ #if HAVE_CONFIG_H # include #endif #include #include #include #ifdef __cplusplus extern "C" { #endif #define TRANSPARENT 256 static void put_image_in_screen(Gif_Stream *gfs, Gif_Image *gfi, uint16_t *screen) { int transparent = gfi->transparent; unsigned x, y; unsigned w = gfi->width; unsigned h = gfi->height; if (gfi->left + w > gfs->screen_width) w = gfs->screen_width - gfi->left; if (gfi->top + h > gfs->screen_height) h = gfs->screen_height - gfi->top; for (y = 0; y < h; y++) { uint16_t *move = screen + (unsigned) gfs->screen_width * (y + gfi->top) + gfi->left; uint8_t *line = gfi->img[y]; for (x = 0; x < w; x++, move++, line++) if (*line != transparent) *move = *line; } } static void put_background_in_screen(Gif_Stream *gfs, Gif_Image *gfi, uint16_t *screen) { uint16_t solid; unsigned x, y; unsigned w = gfi->width; unsigned h = gfi->height; if (gfi->left + w > gfs->screen_width) w = gfs->screen_width - gfi->left; if (gfi->top + h > gfs->screen_height) h = gfs->screen_height - gfi->top; if (gfi->transparent < 0 && gfs->images[0]->transparent < 0 && gfs->global && gfs->background < gfs->global->ncol) solid = gfs->background; else solid = TRANSPARENT; for (y = 0; y < h; y++) { uint16_t *move = screen + (unsigned) gfs->screen_width * (y + gfi->top) + gfi->left; for (x = 0; x < w; x++, move++) *move = solid; } } static int create_image_data(Gif_Stream *gfs, Gif_Image *gfi, uint16_t *screen, uint8_t *new_data, int *used_transparent) { int have[257]; int transparent = -1; unsigned pos, size = (unsigned) gfs->screen_width * (unsigned) gfs->screen_height; uint16_t *move; int i; /* mark colors used opaquely in the image */ assert(TRANSPARENT == 256); for (i = 0; i < 257; i++) have[i] = 0; for (pos = 0, move = screen; pos != size; ++pos, move++) have[*move] = 1; /* the new transparent color is a color unused in either */ if (have[TRANSPARENT]) { for (i = 0; i < 256 && transparent < 0; i++) if (!have[i]) transparent = i; if (transparent < 0) goto error; if (transparent >= gfs->global->ncol) { Gif_ReArray(gfs->global->col, Gif_Color, 256); if (!gfs->global->col) goto error; gfs->global->ncol = transparent + 1; } } /* map the wide image onto the new data */ *used_transparent = 0; for (pos = 0, move = screen; pos != size; ++pos, move++, new_data++) if (*move == TRANSPARENT) { *new_data = transparent; *used_transparent = 1; } else *new_data = *move; gfi->transparent = transparent; return 1; error: return 0; } static int unoptimize_image(Gif_Stream *gfs, Gif_Image *gfi, uint16_t *screen) { unsigned size = (unsigned) gfs->screen_width * (unsigned) gfs->screen_height; int used_transparent; uint8_t *new_data = Gif_NewArray(uint8_t, size); uint16_t *new_screen = screen; if (!new_data) return 0; /* Oops! May need to uncompress it */ Gif_UncompressImage(gfs, gfi); Gif_ReleaseCompressedImage(gfi); if (gfi->disposal == GIF_DISPOSAL_PREVIOUS) { new_screen = Gif_NewArray(uint16_t, size); if (!new_screen) return 0; memcpy(new_screen, screen, size * sizeof(uint16_t)); } put_image_in_screen(gfs, gfi, new_screen); if (!create_image_data(gfs, gfi, new_screen, new_data, &used_transparent)) { Gif_DeleteArray(new_data); return 0; } if (gfi->disposal == GIF_DISPOSAL_PREVIOUS) Gif_DeleteArray(new_screen); else if (gfi->disposal == GIF_DISPOSAL_BACKGROUND) put_background_in_screen(gfs, gfi, screen); gfi->left = 0; gfi->top = 0; gfi->width = gfs->screen_width; gfi->height = gfs->screen_height; gfi->disposal = used_transparent; Gif_SetUncompressedImage(gfi, new_data, Gif_Free, 0); return 1; } static int no_more_transparency(Gif_Image *gfi1, Gif_Image *gfi2) { int t1 = gfi1->transparent, t2 = gfi2->transparent, y; if (t1 < 0) return 1; for (y = 0; y < gfi1->height; ++y) { uint8_t *d1 = gfi1->img[y], *d2 = gfi2->img[y], *ed1 = d1 + gfi1->width; while (d1 < ed1) { if (*d1 == t1 && *d2 != t2) return 0; ++d1, ++d2; } } return 1; } int Gif_FullUnoptimize(Gif_Stream *gfs, int flags) { int ok = 1; int i; unsigned pos, size; uint16_t *screen; uint16_t background; Gif_Image *gfi; if (gfs->nimages < 1) return 1; for (i = 0; i < gfs->nimages; i++) if (gfs->images[i]->local) return 0; if (!gfs->global) return 0; Gif_CalculateScreenSize(gfs, 0); size = (unsigned) gfs->screen_width * (unsigned) gfs->screen_height; screen = Gif_NewArray(uint16_t, size); gfi = gfs->images[0]; if (gfi->transparent < 0 && gfs->global && gfs->background < gfs->global->ncol) background = gfs->background; else background = TRANSPARENT; for (pos = 0; pos != size; ++pos) screen[pos] = background; for (i = 0; i < gfs->nimages; i++) if (!unoptimize_image(gfs, gfs->images[i], screen)) ok = 0; if (ok) { if (flags & GIF_UNOPTIMIZE_SIMPLEST_DISPOSAL) { /* set disposal based on use of transparency. If (every transparent pixel in frame i is also transparent in frame i - 1), then frame i - 1 gets disposal ASIS; otherwise, disposal BACKGROUND. */ for (i = 0; i < gfs->nimages; ++i) if (i == gfs->nimages - 1 || no_more_transparency(gfs->images[i+1], gfs->images[i])) gfs->images[i]->disposal = GIF_DISPOSAL_NONE; else gfs->images[i]->disposal = GIF_DISPOSAL_BACKGROUND; } else for (i = 0; i < gfs->nimages; ++i) gfs->images[i]->disposal = GIF_DISPOSAL_BACKGROUND; } Gif_DeleteArray(screen); return ok; } int Gif_Unoptimize(Gif_Stream *gfs) { return Gif_FullUnoptimize(gfs, 0); } #ifdef __cplusplus } #endif gifsicle-1.96/src/gifview.c000066400000000000000000001157361475770763400156720ustar00rootroot00000000000000/* gifview.c - gifview's main loop. Copyright (C) 1997-2025 Eddie Kohler, ekohler@gmail.com This file is part of gifview, in the gifsicle package. Gifview is free software. It is distributed under the GNU Public License, version 2; you can copy, distribute, or alter it at will, as long as this notice is kept intact and this source code is made available. There is no warranty, express or implied. */ #include #ifdef X_DISPLAY_MISSING #error "You can't compile gifview without X." #endif #include #include #include #include #include #include #include #if HAVE_SYS_SELECT_H # include #endif #include #include #include #include #include #include #include #if HAVE_UNISTD_H # include #endif #ifdef __cplusplus #define EXTERN extern "C" #else #define EXTERN extern #endif /***** * TIME STUFF (from xwrits) **/ #define MICRO_PER_SEC 1000000 #define xwADDTIME(result, a, b) do { \ (result).tv_sec = (a).tv_sec + (b).tv_sec; \ if (((result).tv_usec = (a).tv_usec + (b).tv_usec) >= MICRO_PER_SEC) { \ (result).tv_sec++; \ (result).tv_usec -= MICRO_PER_SEC; \ } } while (0) #define xwSUBTIME(result, a, b) do { \ (result).tv_sec = (a).tv_sec - (b).tv_sec; \ if (((result).tv_usec = (a).tv_usec - (b).tv_usec) < 0) { \ (result).tv_sec--; \ (result).tv_usec += MICRO_PER_SEC; \ } } while (0) #define xwSETMINTIME(a, b) do { \ if ((b).tv_sec < (a).tv_sec || \ ((b).tv_sec == (a).tv_sec && (b).tv_usec < (a).tv_usec)) \ (a) = (b); \ } while (0) #define xwTIMEGEQ(a, b) ((a).tv_sec > (b).tv_sec || \ ((a).tv_sec == (b).tv_sec && (a).tv_usec >= (b).tv_usec)) #define xwTIMEGT(a, b) ((a).tv_sec > (b).tv_sec || \ ((a).tv_sec == (b).tv_sec && (a).tv_usec > (b).tv_usec)) #define xwTIMELEQ0(a) ((a).tv_sec < 0 || ((a).tv_sec == 0 && (a).tv_usec <= 0)) #ifdef X_GETTIMEOFDAY # define xwGETTIMEOFDAY(a) X_GETTIMEOFDAY(a) #elif GETTIMEOFDAY_PROTO == 0 EXTERN int gettimeofday(struct timeval *, struct timezone *); # define xwGETTIMEOFDAY(a) gettimeofday((a), 0) #elif GETTIMEOFDAY_PROTO == 1 # define xwGETTIMEOFDAY(a) gettimeofday((a)) #else # define xwGETTIMEOFDAY(a) gettimeofday((a), 0) #endif #define xwGETTIME(a) do { xwGETTIMEOFDAY(&(a)); xwSUBTIME((a), (a), genesis_time); } while (0) struct timeval genesis_time; /***** * THE VIEWER STRUCTURE **/ static unsigned pixel_memory_limit_kb = 40000; static unsigned pixel_memory_kb; typedef struct Gt_Viewer { Display *display; int screen_number; Visual *visual; int depth; Colormap colormap; Gif_XContext *gfx; Cursor arrow_cursor; Cursor wait_cursor; Window parent; int top_level; Window window; int use_window; int width; int height; int resizable; int being_deleted; Gif_Stream *gfs; const char *name; const char *title; Gif_Image **im; int nim; Pixmap pixmap; int im_pos; int was_unoptimized; Gif_XFrame *unoptimized_frames; int n_unoptimized_frames; struct Gt_Viewer *next; int can_animate; int animating; int unoptimizing; int scheduled; int preparing; struct Gt_Viewer *anim_next; struct timeval timer; int anim_loop; } Gt_Viewer; const char *program_name = "gifview"; static Clp_Parser* clp; static const char *cur_display_name = 0; static Display *cur_display = 0; static const char *cur_geometry_spec = 0; static Cursor cur_arrow_cursor = 0; static Cursor cur_wait_cursor = 0; static const char *cur_resource_name; static const char *cur_window_title = 0; static Window cur_use_window = None; static int cur_use_window_new = 0; static const char *cur_background_color = "black"; static Gt_Viewer *viewers; static Gt_Viewer *animations; static int animating = 0; static int unoptimizing = 0; static int install_colormap = 0; static int interactive = 1; static int min_delay = 0; static int fallback_delay = 0; static struct timeval preparation_time; #define DISPLAY_OPT 300 #define UNOPTIMIZE_OPT 301 #define VERSION_OPT 302 #define ANIMATE_OPT 303 #define GEOMETRY_OPT 304 #define NAME_OPT 305 #define HELP_OPT 306 #define WINDOW_OPT 307 #define INSTALL_COLORMAP_OPT 308 #define INTERACTIVE_OPT 309 #define BACKGROUND_OPT 310 #define NEW_WINDOW_OPT 311 #define TITLE_OPT 312 #define MIN_DELAY_OPT 313 #define FALLBACK_DELAY_OPT 314 #define MEMORY_LIMIT_OPT 315 #define WINDOW_TYPE (Clp_ValFirstUser) const Clp_Option options[] = { { "animate", 'a', ANIMATE_OPT, 0, Clp_Negate }, { "background", 'b', BACKGROUND_OPT, Clp_ValString, 0 }, { "bg", 't', BACKGROUND_OPT, Clp_ValString, 0 }, { "display", 'd', DISPLAY_OPT, Clp_ValStringNotOption, 0 }, { "geometry", 'g', GEOMETRY_OPT, Clp_ValString, 0 }, { "install-colormap", 'i', INSTALL_COLORMAP_OPT, 0, Clp_Negate }, { "interactive", 'e', INTERACTIVE_OPT, 0, Clp_Negate }, { "help", 0, HELP_OPT, 0, 0 }, { "memory-limit", 0, MEMORY_LIMIT_OPT, Clp_ValUnsigned, Clp_Negate }, { "min-delay", 0, MIN_DELAY_OPT, Clp_ValInt, Clp_Negate }, { "fallback-delay", 0, FALLBACK_DELAY_OPT, Clp_ValInt, Clp_Negate }, { "name", 0, NAME_OPT, Clp_ValString, 0 }, { "title", 'T', TITLE_OPT, Clp_ValString, 0 }, { "unoptimize", 'U', UNOPTIMIZE_OPT, 0, Clp_Negate }, { "version", 0, VERSION_OPT, 0, 0 }, { "window", 'w', WINDOW_OPT, WINDOW_TYPE, 0 }, { "new-window", 0, NEW_WINDOW_OPT, WINDOW_TYPE, 0 } }; /***** * Diagnostics **/ void fatal_error(const char* format, ...) { char buf[BUFSIZ]; int n = snprintf(buf, BUFSIZ, "%s: ", program_name); va_list val; va_start(val, format); Clp_vsnprintf(clp, buf + n, BUFSIZ - n, format, val); va_end(val); fputs(buf, stderr); exit(1); } void error(const char* format, ...) { char buf[BUFSIZ]; int n = snprintf(buf, BUFSIZ, "%s: ", program_name); va_list val; va_start(val, format); Clp_vsnprintf(clp, buf + n, BUFSIZ - n, format, val); va_end(val); fputs(buf, stderr); } void warning(const char* format, ...) { char buf[BUFSIZ]; int n = snprintf(buf, BUFSIZ, "%s: warning: ", program_name); va_list val; va_start(val, format); Clp_vsnprintf(clp, buf + n, BUFSIZ - n, format, val); va_end(val); fputs(buf, stderr); } void short_usage(void) { Clp_fprintf(clp, stderr, "\ Usage: %s [--display DISPLAY] [OPTION]... [FILE | FRAME]...\n\ Try %<%s --help%> for more information.\n", program_name, program_name); } void usage(void) { Clp_fprintf(clp, stdout, "\ % is a lightweight GIF viewer for X. It can display animated GIFs as\n\ slideshows, one frame at a time, or as animations.\n\ \n\ Usage: %s [--display DISPLAY] [OPTION]... [FILE | FRAME]...\n\n", program_name); Clp_fprintf(clp, stdout, "\ Options are:\n\ -a, --animate Animate multiframe GIFs.\n\ -U, --unoptimize Unoptimize displayed GIFs.\n\ -d, --display DISPLAY Set display to DISPLAY.\n\ --name NAME Set application resource name to NAME.\n\ -g, --geometry GEOMETRY Set window geometry.\n\ -T, --title TITLE Set window title.\n"); Clp_fprintf(clp, stdout, "\ -w, --window WINDOW Show GIF in existing WINDOW.\n\ --new-window WINDOW Show GIF in new child of existing WINDOW.\n\ -i, --install-colormap Use a private colormap.\n\ --bg, --background COLOR Use COLOR for transparent pixels.\n\ --min-delay DELAY Set minimum frame delay to DELAY/100 sec.\n\ --fallback-delay DELAY Set fallback frame delay to DELAY/100 sec.\n\ +e, --no-interactive Ignore buttons and keystrokes.\n"); Clp_fprintf(clp, stdout, "\ --memory-limit LIM Cache at most LIM megabytes of animation.\n\ --help Print this message and exit.\n\ --version Print version number and exit.\n\ \n\ Frame selections: #num, #num1-num2, #num1-, #name\n\n"); Clp_fprintf(clp, stdout, "\ Keystrokes:\n\ [N]/[Space] Go to next frame. [P]/[B] Go to previous frame.\n\ [R]/[<] Go to first frame. [>] Go to last frame.\n\ [ESC] Stop animation. [S]/[A] Toggle animation.\n\ [U] Toggle unoptimization. [Backspace]/[W] Delete window.\n\ [Q] Quit.\n\ \n\ Left mouse button goes to next frame, right mouse button deletes window.\n\ \n\ Report bugs to .\n"); } /***** * Window creation **/ #if defined(__cplusplus) || defined(c_plusplus) #define VISUAL_CLASS c_class #else #define VISUAL_CLASS class #endif static void choose_visual(Gt_Viewer *viewer) { Display *display = viewer->display; int screen_number = viewer->screen_number; VisualID default_visualid = DefaultVisual(display, screen_number)->visualid; XVisualInfo visi_template; int nv, i; XVisualInfo *v, *best_v = 0; Gt_Viewer *trav; /* Look for an existing Gt_Viewer with the same display and screen number */ if (!install_colormap) for (trav = viewers; trav; trav = trav->next) if (trav != viewer && trav->display == display && trav->screen_number == screen_number) { viewer->visual = trav->visual; viewer->depth = trav->depth; viewer->colormap = trav->colormap; viewer->gfx = trav->gfx; viewer->gfx->refcount++; return; } /* Find the default visual's XVisualInfo & put it in best_v */ visi_template.screen = screen_number; v = XGetVisualInfo(display, VisualScreenMask, &visi_template, &nv); for (i = 0; i < nv && !best_v; i++) if (v[i].visualid == default_visualid) best_v = &v[i]; if (!best_v) { /* This should never happen. If we can't find the default visual's XVisualInfo, we just use the default visual */ viewer->visual = DefaultVisual(display, screen_number); viewer->depth = DefaultDepth(display, screen_number); viewer->colormap = DefaultColormap(display, screen_number); } else { /* Which visual to choose? This isn't exactly a simple decision, since we want to avoid colormap flashing while choosing a nice visual. So here's the algorithm: Prefer the default visual, or take a TrueColor visual with strictly greater depth. */ for (i = 0; i < nv; i++) if (v[i].depth > best_v->depth && v[i].VISUAL_CLASS == TrueColor) best_v = &v[i]; viewer->visual = best_v->visual; viewer->depth = best_v->depth; if (best_v->visualid != default_visualid || (best_v->VISUAL_CLASS == PseudoColor && install_colormap)) viewer->colormap = XCreateColormap(display, RootWindow(display, screen_number), viewer->visual, AllocNone); else viewer->colormap = DefaultColormap(display, screen_number); } viewer->gfx = Gif_NewXContextFromVisual (display, screen_number, viewer->visual, viewer->depth, viewer->colormap); viewer->gfx->refcount++; if (v) XFree(v); } Gt_Viewer * new_viewer(Display *display, Gif_Stream *gfs, const char *name) { Gt_Viewer *viewer; int i; /* Make the Gt_Viewer structure */ viewer = Gif_New(Gt_Viewer); viewer->display = display; if (cur_use_window) { XWindowAttributes attr; if (cur_use_window == (Window)(-1)) { /* means use root window */ viewer->screen_number = DefaultScreen(display); cur_use_window = RootWindow(display, viewer->screen_number); } XGetWindowAttributes(display, cur_use_window, &attr); viewer->screen_number = -1; for (i = 0; i < ScreenCount(display); i++) if (ScreenOfDisplay(display, i) == attr.screen) viewer->screen_number = i; assert(viewer->screen_number >= 0); viewer->visual = attr.visual; viewer->depth = attr.depth; viewer->colormap = attr.colormap; viewer->gfx = Gif_NewXContextFromVisual (display, viewer->screen_number, viewer->visual, viewer->depth, viewer->colormap); viewer->gfx->refcount++; /* Before -- use root window, if that's what we were given; otherwise, create a child of the window we were given */ /* 13.Nov.2001 - don't make a child of the window we were given! */ if (cur_use_window_new) { viewer->window = None; viewer->use_window = 0; } else { viewer->window = cur_use_window; viewer->use_window = 1; } viewer->parent = cur_use_window; viewer->top_level = 0; viewer->resizable = 0; } else { viewer->screen_number = DefaultScreen(display); choose_visual(viewer); viewer->window = None; viewer->parent = RootWindow(display, viewer->screen_number); viewer->use_window = 0; viewer->top_level = 1; viewer->resizable = 1; } /* assign background color */ if (cur_background_color) { XColor color; if (!XParseColor(viewer->display, viewer->colormap, cur_background_color, &color)) { error("invalid background color %<%s%>\n", cur_background_color); cur_background_color = 0; } else if (!XAllocColor(viewer->display, viewer->colormap, &color)) warning("can%,t allocate background color\n"); else { unsigned long pixel = color.pixel; Gif_XContext *gfx = viewer->gfx; if (pixel != gfx->transparent_pixel && gfx->refcount > 1) { /* copy X context */ viewer->gfx = Gif_NewXContextFromVisual (gfx->display, gfx->screen_number, gfx->visual, gfx->depth, gfx->colormap); viewer->gfx->refcount++; gfx->refcount--; } viewer->gfx->transparent_pixel = pixel; } } if (!cur_arrow_cursor) { cur_arrow_cursor = XCreateFontCursor(display, XC_left_ptr); cur_wait_cursor = XCreateFontCursor(display, XC_watch); } viewer->arrow_cursor = cur_arrow_cursor; viewer->wait_cursor = cur_wait_cursor; viewer->being_deleted = 0; viewer->gfs = gfs; gfs->refcount++; viewer->name = name; viewer->title = cur_window_title; viewer->nim = Gif_ImageCount(gfs); viewer->im = Gif_NewArray(Gif_Image *, viewer->nim); for (i = 0; i < viewer->nim; i++) viewer->im[i] = gfs->images[i]; viewer->pixmap = None; viewer->im_pos = -1; viewer->was_unoptimized = 0; viewer->unoptimized_frames = Gif_NewXFrames(gfs); viewer->n_unoptimized_frames = 0; viewer->next = viewers; viewers = viewer; viewer->animating = 0; viewer->unoptimizing = unoptimizing; viewer->scheduled = 0; viewer->preparing = 0; viewer->anim_next = 0; viewer->anim_loop = 0; viewer->timer.tv_sec = viewer->timer.tv_usec = 0; return viewer; } void delete_viewer(Gt_Viewer *viewer) { Gt_Viewer *prev = 0, *trav; if (viewer->pixmap && !viewer->was_unoptimized) XFreePixmap(viewer->display, viewer->pixmap); for (trav = viewers; trav != viewer; prev = trav, trav = trav->next) ; if (prev) prev->next = viewer->next; else viewers = viewer->next; Gif_DeleteXFrames(viewer->gfx, viewer->gfs, viewer->unoptimized_frames); Gif_DeleteStream(viewer->gfs); Gif_DeleteArray(viewer->im); Gif_DeleteXContext(viewer->gfx); Gif_Delete(viewer); } static Gt_Viewer * next_viewer(Gif_Stream *gfs, const char *name) { Gt_Viewer *viewer = new_viewer(cur_display, gfs, name); cur_use_window = None; return viewer; } static Gt_Viewer * get_input_stream(const char *name) { FILE *f; Gif_Stream *gfs = 0; if (name == 0 || strcmp(name, "-") == 0) { #ifndef OUTPUT_GIF_TO_TERMINAL if (isatty(fileno(stdin))) { error(": is a terminal\n"); return NULL; } #endif f = stdin; #if defined(_MSDOS) || defined(_WIN32) _setmode(_fileno(stdin), _O_BINARY); #elif defined(__DJGPP__) setmode(fileno(stdin), O_BINARY); #elif defined(__EMX__) _fsetmode(stdin, "b"); #endif name = ""; } else f = fopen(name, "rb"); if (!f) { error("%s: %s\n", name, strerror(errno)); return 0; } gfs = Gif_FullReadFile(f, GIF_READ_COMPRESSED, 0, 0); fclose(f); if (!gfs || Gif_ImageCount(gfs) == 0) { error("%s: file not in GIF format\n", name); Gif_DeleteStream(gfs); return 0; } if (!cur_display) { cur_display = XOpenDisplay(cur_display_name); if (!cur_display) { error("can%,t open display\n"); return 0; } } return next_viewer(gfs, name); } /***** * Schedule stuff **/ void switch_animating(Gt_Viewer *viewer, int animating) { int i; Gif_Stream *gfs = viewer->gfs; if (animating == viewer->animating || !viewer->can_animate) return; for (i = 0; i < gfs->nimages; i++) viewer->im[i] = gfs->images[i]; viewer->animating = animating; if (!animating) viewer->timer.tv_sec = viewer->timer.tv_usec = 0; } void unschedule(Gt_Viewer *viewer) { Gt_Viewer *prev, *trav; if (!viewer->scheduled) return; for (prev = 0, trav = animations; trav; prev = trav, trav = trav->anim_next) if (trav == viewer) break; if (trav) { if (prev) prev->anim_next = viewer->anim_next; else animations = viewer->anim_next; } viewer->scheduled = 0; viewer->timer.tv_sec = viewer->timer.tv_usec = 0; } void schedule(Gt_Viewer *viewer) { Gt_Viewer *prev, *trav; if (viewer->scheduled) unschedule(viewer); prev = 0; for (trav = animations; trav; prev = trav, trav = trav->anim_next) if (xwTIMEGEQ(trav->timer, viewer->timer)) break; if (prev) { viewer->anim_next = trav; prev->anim_next = viewer; } else { viewer->anim_next = animations; animations = viewer; } viewer->scheduled = 1; } void schedule_next_frame(Gt_Viewer *viewer) { struct timeval interval; int delay = viewer->im[viewer->im_pos]->delay; int next_pos = viewer->im_pos + 1; if (delay < 1) delay = fallback_delay; if (delay < min_delay) delay = min_delay; if (next_pos == viewer->nim) next_pos = 0; if (viewer->timer.tv_sec == 0 && viewer->timer.tv_usec == 0) xwGETTIME(viewer->timer); interval.tv_sec = delay / 100; interval.tv_usec = (delay % 100) * (MICRO_PER_SEC / 100); if (delay == 0) interval.tv_usec = 2000; xwADDTIME(viewer->timer, viewer->timer, interval); /* 1.Aug.2002 - leave some time to prepare the frame if necessary */ if (viewer->unoptimized_frames[next_pos].pixmap) { xwSUBTIME(viewer->timer, viewer->timer, preparation_time); viewer->preparing = 1; } schedule(viewer); } /***** * X stuff **/ int parse_geometry(const char *const_g, XSizeHints *sh, int screen_width, int screen_height) { char *g = (char *)const_g; sh->flags = 0; if (isdigit(*g)) { sh->flags |= USSize; sh->width = strtol(g, &g, 10); if (g[0] == 'x' && isdigit(g[1])) sh->height = strtol(g + 1, &g, 10); else goto error; } else if (!*g) goto error; if (*g == '+' || *g == '-') { int x_minus, y_minus; sh->flags |= USPosition | PWinGravity; x_minus = *g == '-'; sh->x = strtol(g + 1, &g, 10); if (x_minus) sh->x = screen_width - sh->x - sh->width; y_minus = *g == '-'; if (*g == '-' || *g == '+') sh->y = strtol(g + 1, &g, 10); else goto error; if (y_minus) sh->y = screen_height - sh->y - sh->height; if (x_minus) sh->win_gravity = y_minus ? SouthEastGravity : NorthEastGravity; else sh->win_gravity = y_minus ? SouthWestGravity : NorthWestGravity; } else if (*g) goto error; return 1; error: warning("bad geometry specification\n"); sh->flags = 0; return 0; } static Atom wm_delete_window_atom; static Atom wm_protocols_atom; void create_viewer_window(Gt_Viewer *viewer, int w, int h) { Display *display = viewer->display; char *stringlist[2]; XTextProperty window_name, icon_name; XClassHint classh; XSizeHints *sizeh = XAllocSizeHints(); /* sets all fields to 0 */ /* Set the window's geometry */ sizeh->width = w ? w : 1; sizeh->height = h ? h : 1; if (cur_geometry_spec) { int scr_width = DisplayWidth(viewer->display, viewer->screen_number); int scr_height = DisplayHeight(viewer->display, viewer->screen_number); parse_geometry(cur_geometry_spec, sizeh, scr_width, scr_height); } /* Open the display and create the window */ if (!viewer->window) { XSetWindowAttributes x_set_attr; unsigned long x_set_attr_mask; x_set_attr.colormap = viewer->colormap; x_set_attr.backing_store = NotUseful; x_set_attr.save_under = False; x_set_attr.border_pixel = 0; x_set_attr.background_pixel = 0; x_set_attr_mask = CWColormap | CWBorderPixel | CWBackPixel | CWBackingStore | CWSaveUnder; viewer->window = XCreateWindow (display, viewer->parent, sizeh->x, sizeh->y, sizeh->width, sizeh->height, 0, viewer->depth, InputOutput, viewer->visual, x_set_attr_mask, &x_set_attr); XDefineCursor(display, viewer->window, viewer->arrow_cursor); } /* If user gave us geometry, don't change the size later */ if (sizeh->flags & USSize) viewer->resizable = 0; viewer->width = w; viewer->height = h; /* Set the window's title and class (for window manager resources) */ if (viewer->top_level) { stringlist[0] = "gifview"; stringlist[1] = 0; XStringListToTextProperty(stringlist, 1, &window_name); XStringListToTextProperty(stringlist, 1, &icon_name); classh.res_name = (char *)cur_resource_name; classh.res_class = "Gifview"; XSetWMProperties(display, viewer->window, &window_name, &icon_name, NULL, 0, sizeh, NULL, &classh); XFree(window_name.value); XFree(icon_name.value); if (!wm_delete_window_atom) { wm_delete_window_atom = XInternAtom(display, "WM_DELETE_WINDOW", False); wm_protocols_atom = XInternAtom(display, "WM_PROTOCOLS", False); } XSetWMProtocols(display, viewer->window, &wm_delete_window_atom, 1); } if (interactive) XSelectInput(display, viewer->window, ButtonPressMask | KeyPressMask | StructureNotifyMask); else XSelectInput(display, viewer->window, StructureNotifyMask); XFree(sizeh); } void pre_delete_viewer(Gt_Viewer *viewer) { if (viewer->being_deleted) return; viewer->being_deleted = 1; if (viewer->scheduled) unschedule(viewer); if (viewer->window && !viewer->use_window) XDestroyWindow(viewer->display, viewer->window); else delete_viewer(viewer); } Gt_Viewer * find_viewer(Display *display, Window window) { Gt_Viewer *v; for (v = viewers; v; v = v->next) if (v->display == display && v->window == window) return v; return 0; } void set_viewer_name(Gt_Viewer *viewer, int slow_number) { Gif_Image *gfi; char *strs[2]; char *identifier; XTextProperty name_prop; int im_pos = (slow_number >= 0 ? slow_number : viewer->im_pos); int len; if (!viewer->top_level || im_pos >= viewer->nim || viewer->being_deleted) return; gfi = viewer->im[im_pos]; len = strlen(viewer->title) + strlen(viewer->name) + 14; identifier = (slow_number >= 0 ? (char *)0 : gfi->identifier); if (identifier) len += 2 + strlen(identifier); strs[0] = Gif_NewArray(char, len); if (strcmp(viewer->title, "gifview") != 0) snprintf(strs[0], len, "%s", viewer->title); else if (slow_number >= 0) snprintf(strs[0], len, "gifview: %s [#%d]", viewer->name, im_pos); else if (viewer->nim == 1 && identifier) snprintf(strs[0], len, "gifview: %s #%s", viewer->name, identifier); else if (viewer->animating || viewer->nim == 1) snprintf(strs[0], len, "gifview: %s", viewer->name); else if (!identifier) snprintf(strs[0], len, "gifview: %s #%d", viewer->name, im_pos); else snprintf(strs[0], len, "gifview: %s #%d #%s", viewer->name, im_pos, identifier); strs[1] = 0; XStringListToTextProperty(strs, 1, &name_prop); XSetWMName(viewer->display, viewer->window, &name_prop); XSetWMIconName(viewer->display, viewer->window, &name_prop); XFree(name_prop.value); Gif_DeleteArray(strs[0]); } static unsigned screen_memory_kb(const Gt_Viewer* viewer) { return 1 + ((unsigned) (viewer->gfs->screen_width * viewer->gfs->screen_height) / 334); } static Pixmap unoptimized_frame(Gt_Viewer *viewer, int frame, int slow) { /* create a new unoptimized frame if necessary */ if (!viewer->unoptimized_frames[frame].pixmap) { (void) Gif_XNextImage(viewer->gfx, viewer->gfs, frame, viewer->unoptimized_frames); pixel_memory_kb += screen_memory_kb(viewer); viewer->unoptimized_frames[viewer->n_unoptimized_frames].user_data = frame; ++viewer->n_unoptimized_frames; if (slow) { set_viewer_name(viewer, frame); XFlush(viewer->display); } } /* kill some old frames if over the memory limit */ while (pixel_memory_limit_kb != (unsigned) -1 && pixel_memory_limit_kb < pixel_memory_kb && viewer->n_unoptimized_frames > 1) { int killidx, killframe, i = 0; do { killidx = random() % viewer->n_unoptimized_frames; killframe = viewer->unoptimized_frames[killidx].user_data; ++i; } while (killframe == frame || (i < 10 && killframe > frame && killframe < frame + 5) || (i < 10 && (killframe % 50) == 0)); XFreePixmap(viewer->display, viewer->unoptimized_frames[killframe].pixmap); viewer->unoptimized_frames[killframe].pixmap = None; --viewer->n_unoptimized_frames; viewer->unoptimized_frames[killidx].user_data = viewer->unoptimized_frames[viewer->n_unoptimized_frames].user_data; pixel_memory_kb -= screen_memory_kb(viewer); } return viewer->unoptimized_frames[frame].pixmap; } void prepare_frame(Gt_Viewer *viewer, int frame) { Display *display = viewer->display; Window window = viewer->window; int changed_cursor = 0; if (viewer->being_deleted || !viewer->animating) return; if (frame < 0 || frame > viewer->nim - 1) frame = 0; /* Change cursor if we need to wait. */ if ((viewer->animating || viewer->unoptimizing) && !viewer->unoptimized_frames[frame].pixmap) { if (frame > viewer->im_pos + 10 || frame < viewer->im_pos) { changed_cursor = 1; XDefineCursor(display, window, viewer->wait_cursor); XFlush(display); } } /* Prepare the frame */ (void) unoptimized_frame(viewer, frame, changed_cursor && !viewer->animating); /* Restore cursor */ if (changed_cursor) XDefineCursor(display, window, viewer->arrow_cursor); /* schedule actual view of window */ xwADDTIME(viewer->timer, viewer->timer, preparation_time); viewer->preparing = 0; schedule(viewer); } void view_frame(Gt_Viewer *viewer, int frame) { Display *display = viewer->display; Window window = viewer->window; Pixmap old_pixmap = viewer->pixmap; int need_set_name = 0; if (viewer->being_deleted) return; if (frame < 0) frame = 0; if (frame > viewer->nim - 1 && viewer->animating) { int loopcount = viewer->gfs->loopcount; if (loopcount == 0 || loopcount > viewer->anim_loop) { viewer->anim_loop++; frame = 0; } else { switch_animating(viewer, 0); need_set_name = 1; } } if (frame > viewer->nim - 1) frame = viewer->nim - 1; if (frame != viewer->im_pos) { Gif_Image *gfi = viewer->im[frame]; int width, height, changed_cursor = 0; /* Change cursor if we need to wait. */ if ((viewer->animating || viewer->unoptimizing) && !viewer->unoptimized_frames[frame].pixmap) { if (frame > viewer->im_pos + 10 || frame < viewer->im_pos) { changed_cursor = 1; XDefineCursor(display, window, viewer->wait_cursor); XFlush(display); } } /* 5/26/98 Do some noodling around to try and use memory most effectively. If animating, keep the uncompressed frame; otherwise, throw it away. */ if (viewer->animating || viewer->unoptimizing) viewer->pixmap = unoptimized_frame(viewer, frame, changed_cursor && !viewer->animating); else viewer->pixmap = Gif_XImage(viewer->gfx, viewer->gfs, gfi); /* put the image on the window */ if (viewer->animating || viewer->unoptimizing) width = viewer->gfs->screen_width, height = viewer->gfs->screen_height; else width = Gif_ImageWidth(gfi), height = Gif_ImageHeight(gfi); if (!window) { create_viewer_window(viewer, width, height); window = viewer->window; } XSetWindowBackgroundPixmap(display, window, viewer->pixmap); if (old_pixmap || viewer->use_window) /* clear existing window */ XClearWindow(display, window); /* Only change size after changing pixmap. */ if ((viewer->width != width || viewer->height != height) && viewer->resizable) { XWindowChanges winch; winch.width = viewer->width = width; winch.height = viewer->height = height; XReconfigureWMWindow (display, window, viewer->screen_number, CWWidth | CWHeight, &winch); } /* Get rid of old pixmaps */ if (!viewer->was_unoptimized && old_pixmap) XFreePixmap(display, old_pixmap); viewer->was_unoptimized = viewer->animating || viewer->unoptimizing; /* Restore cursor */ if (changed_cursor) XDefineCursor(display, window, viewer->arrow_cursor); /* Do we need a new name? */ if ((!viewer->animating && Gif_ImageCount(viewer->gfs) > 1) || old_pixmap == None) need_set_name = 1; } viewer->im_pos = frame; viewer->preparing = 0; if (need_set_name) set_viewer_name(viewer, -1); if (!old_pixmap && !viewer->use_window) /* first image; map the window */ XMapRaised(display, window); else if (viewer->animating) /* only schedule next frame if image is already mapped */ schedule_next_frame(viewer); } /***** * Command line arguments: marking frames, being done with streams **/ int frame_argument(Gt_Viewer *viewer, const char *arg) { const char *c = arg; int n1 = 0; /* Get a number range (#x, #x-y, #x-, or #-y). First, read x. */ if (isdigit(c[0])) n1 = strtol(arg, (char **)&c, 10); /* It really was a number range only if c is now at the end of the argument. */ if (c[0] != 0) { Gif_Image *gfi = Gif_GetNamedImage(viewer->gfs, c); if (!gfi) error("no frame named %<%s%>\n", c); else n1 = Gif_ImageNumber(viewer->gfs, gfi); } else { if (n1 < 0 || n1 >= viewer->nim) { error("no frame number %d\n", n1); n1 = 0; } } return n1; } void input_stream_done(Gt_Viewer *viewer, int first_frame) { viewer->can_animate = Gif_ImageCount(viewer->gfs) > 1; switch_animating(viewer, animating && viewer->can_animate); if (first_frame < 0) first_frame = 0; view_frame(viewer, first_frame); } void key_press(Gt_Viewer *viewer, XKeyEvent *e) { char buf[32]; KeySym key; int nbuf = XLookupString(e, buf, 32, &key, 0); if (nbuf > 1) buf[0] = 0; /* ignore multikey sequences */ if (key == XK_space || key == XK_F || key == XK_f || key == XK_N || key == XK_n) /* space, N or F: one frame ahead */ view_frame(viewer, viewer->im_pos + 1); else if (key == XK_B || key == XK_b || key == XK_P || key == XK_p) /* B or P: one frame back */ view_frame(viewer, viewer->im_pos - 1); else if (key == XK_W || key == XK_w || key == XK_BackSpace) /* backspace: delete viewer */ pre_delete_viewer(viewer); else if (key == XK_Q || key == XK_q) /* Q: quit application */ exit(0); else if (key == XK_S || key == XK_s || key == XK_a || key == XK_A) { /* S or A: toggle animation */ switch_animating(viewer, !viewer->animating); if (viewer->animating) { int pos = viewer->im_pos; if (viewer->im_pos >= viewer->nim - 1) { pos = 0; viewer->anim_loop = 0; } view_frame(viewer, pos); } else unschedule(viewer); set_viewer_name(viewer, -1); } else if (key == XK_U || key == XK_u) { /* U: toggle unoptimizing */ int pos = viewer->im_pos; viewer->unoptimizing = !viewer->unoptimizing; if (!viewer->animating) { viewer->im_pos = -1; view_frame(viewer, pos); set_viewer_name(viewer, -1); } } else if (key == XK_R || key == XK_r || (nbuf == 1 && buf[0] == '<')) { /* R or <: reset to first frame */ unschedule(viewer); viewer->anim_loop = 0; view_frame(viewer, 0); } else if (nbuf == 1 && buf[0] == '>') { /* >: reset to last frame */ unschedule(viewer); viewer->anim_loop = 0; view_frame(viewer, viewer->nim - 1); } else if (key == XK_Escape && viewer->animating) { /* Escape: stop animation */ switch_animating(viewer, 0); unschedule(viewer); set_viewer_name(viewer, -1); } else if (key == XK_Z || key == XK_z) { /* Z: trigger resizability */ viewer->resizable = !viewer->resizable; } } void loop(void) { struct timeval now, stop_loop, stop_delta; fd_set xfds; XEvent e; int pending; Gt_Viewer *v; Display *display = viewers->display; int x_socket = ConnectionNumber(display); stop_delta.tv_sec = 0; stop_delta.tv_usec = 200000; xwGETTIME(now); FD_ZERO(&xfds); while (viewers) { /* Check for any animations */ /* 13.Feb.2001 - Use the 'pending' counter to avoid a tight loop if all the frames in an animation have delay 0. Reported by Franc,ois Petitjean. */ /* 1.Aug.2002 - Switch to running the loop for max 0.2s. */ xwADDTIME(stop_loop, now, stop_delta); while (animations && xwTIMEGEQ(now, animations->timer) && xwTIMEGEQ(stop_loop, now)) { v = animations; animations = v->anim_next; v->scheduled = 0; if (v->preparing) prepare_frame(v, v->im_pos + 1); else { if (xwTIMEGEQ(now, v->timer)) v->timer = now; view_frame(v, v->im_pos + 1); } xwGETTIME(now); } pending = XPending(display); if (!pending) { /* select() until event arrives */ struct timeval timeout, *timeout_ptr; int retval; if (animations) { xwSUBTIME(timeout, animations->timer, now); timeout_ptr = &timeout; } else timeout_ptr = 0; FD_SET(x_socket, &xfds); retval = select(x_socket + 1, &xfds, 0, 0, timeout_ptr); pending = (retval <= 0 ? 0 : FD_ISSET(x_socket, &xfds)); } if (pending) while (XPending(display)) { XNextEvent(display, &e); v = find_viewer(e.xany.display, e.xany.window); if (v) { if (interactive) { if (e.type == ButtonPress && e.xbutton.button == 1) /* Left mouse button: go to next frame */ view_frame(v, v->im_pos + 1); else if (e.type == ButtonPress && e.xbutton.button == 4) /* mousewheel forward */ view_frame(v, v->im_pos + 1); else if (e.type == ButtonPress && e.xbutton.button == 5) /* mousewheel backward */ view_frame(v, v->im_pos - 1); else if (e.type == ButtonPress && e.xbutton.button == 3) /* Right mouse button: delete window */ pre_delete_viewer(v); else if (e.type == KeyPress) /* Key press: call function */ key_press(v, &e.xkey); } if (e.type == ClientMessage && e.xclient.message_type == wm_protocols_atom && (Atom)(e.xclient.data.l[0]) == wm_delete_window_atom) /* WM_DELETE_WINDOW message: delete window */ pre_delete_viewer(v); else if (e.type == MapNotify && v->animating && v->scheduled == 0) /* Window was just mapped; now, start animating it */ schedule_next_frame(v); else if (e.type == DestroyNotify) /* Once the window has been destroyed, delete related state */ delete_viewer(v); } } xwGETTIME(now); } } int main(int argc, char *argv[]) { Gt_Viewer *viewer = 0; int viewer_given = 0; int any_errors = 0; int first_frame = -1; clp = Clp_NewParser(argc, (const char * const *)argv, sizeof(options) / sizeof(options[0]), options); Clp_SetOptionChar(clp, '+', Clp_ShortNegated); Clp_AddStringListType (clp, WINDOW_TYPE, Clp_AllowNumbers | Clp_StringListLong, "root", (long) -1, (const char*) 0); program_name = cur_resource_name = cur_window_title = Clp_ProgramName(clp); xwGETTIMEOFDAY(&genesis_time); preparation_time.tv_sec = 0; preparation_time.tv_usec = 200000; while (1) { int opt = Clp_Next(clp); switch (opt) { case DISPLAY_OPT: if (cur_display) fatal_error("%<--display%> must come before all other options\n"); cur_display_name = clp->vstr; cur_display = 0; cur_arrow_cursor = cur_wait_cursor = None; break; case TITLE_OPT: cur_window_title = clp->vstr; break; case GEOMETRY_OPT: cur_geometry_spec = clp->vstr; break; case NAME_OPT: cur_resource_name = clp->vstr; break; case UNOPTIMIZE_OPT: unoptimizing = clp->negated ? 0 : 1; break; case BACKGROUND_OPT: cur_background_color = clp->vstr; break; case ANIMATE_OPT: animating = clp->negated ? 0 : 1; break; case INSTALL_COLORMAP_OPT: install_colormap = clp->negated ? 0 : 1; break; case WINDOW_OPT: cur_use_window = clp->val.ul; cur_use_window_new = 0; break; case NEW_WINDOW_OPT: cur_use_window = clp->val.ul; cur_use_window_new = 1; break; case INTERACTIVE_OPT: interactive = clp->negated ? 0 : 1; break; case MIN_DELAY_OPT: min_delay = clp->negated ? 0 : clp->val.i; break; case FALLBACK_DELAY_OPT: fallback_delay = clp->negated ? 0 : clp->val.i; break; case MEMORY_LIMIT_OPT: if (clp->negated || clp->val.u >= ((unsigned) -1 / 1000)) pixel_memory_limit_kb = (unsigned) -1; else pixel_memory_limit_kb = clp->val.u * 1000; break; case VERSION_OPT: printf("gifview (LCDF Gifsicle) %s\n", VERSION); printf("Copyright (C) 1997-2025 Eddie Kohler\n\ This is free software; see the source for copying conditions.\n\ There is NO warranty, not even for merchantability or fitness for a\n\ particular purpose.\n"); exit(0); break; case HELP_OPT: usage(); exit(0); break; case Clp_NotOption: if (clp->vstr[0] == '#') { if (!viewer_given) { viewer = get_input_stream(0); viewer_given = 1; } if (viewer && first_frame >= 0) { /* copy viewer if 2 frame specs given */ input_stream_done(viewer, first_frame); viewer = next_viewer(viewer->gfs, viewer->name); } if (viewer) first_frame = frame_argument(viewer, clp->vstr + 1); } else { if (viewer) input_stream_done(viewer, first_frame); first_frame = -1; viewer = get_input_stream(clp->vstr); viewer_given = 1; } break; case Clp_Done: goto done; case Clp_BadOption: any_errors = 1; break; default: break; } } done: if (!viewer_given) { if (any_errors) { short_usage(); exit(1); } viewer = get_input_stream(0); } if (viewer) input_stream_done(viewer, first_frame); if (viewers) loop(); return 0; } gifsicle-1.96/src/gifwrite.c000066400000000000000000001006151475770763400160400ustar00rootroot00000000000000/* -*- mode: c; c-basic-offset: 2 -*- */ /* gifwrite.c - Functions to write GIFs. Copyright (C) 1997-2025 Eddie Kohler, ekohler@gmail.com This file is part of the LCDF GIF library. The LCDF GIF library is free software. It is distributed under the GNU General Public License, version 2; you can copy, distribute, or alter it at will, as long as this notice is kept intact and this source code is made available. There is no warranty, express or implied. */ #if HAVE_CONFIG_H # include #elif !defined(__cplusplus) && !defined(inline) /* Assume we don't have inline by default */ # define inline #endif #include #include #include #include #include #include "kcolor.h" #ifdef __cplusplus extern "C" { #endif #define WRITE_BUFFER_SIZE 255 #define NODES_SIZE GIF_MAX_CODE #define LINKS_SIZE (GIF_MAX_CODE + 256) /* 1.Aug.1999 - Removed code hashing in favor of an adaptive tree strategy based on Whirlgif-3.04, written by Hans Dinsen-Hansen . Mr. Dinsen-Hansen brought the adaptive tree strategy to my attention and argued at length that it was better than hashing. It runs a lot faster, though it creates identical compression. Each code is represented by a Node. The Nodes form a tree with variable fan-out -- up to 'clear_code' children per Node. There are two kinds of Node, TABLE and LINKS. In a TABLE node, the children are stored in a table indexed by suffix -- thus, it's very efficient to determine a given child. In a LINKS node, the existent children are stored in a linked list. This is slightly slower to access. When a LINKS node gets more than 'MAX_LINKS_TYPE-1' children, it is converted to a TABLE node. (This is why it's an adaptive tree.) Problems with this implementation: MAX_LINKS_TYPE is fixed, so GIFs with very small numbers of colors (2, 4, 8) won't get the speed benefits of TABLE nodes. */ #define TABLE_TYPE 0 #define LINKS_TYPE 1 #define MAX_LINKS_TYPE 5 typedef struct Gif_Node { Gif_Code code; uint8_t type; uint8_t suffix; uint32_t length; struct Gif_Node* sibling; union { struct Gif_Node* s; struct Gif_Node** m; } child; } Gif_Node; typedef struct Gif_LossyNode { Gif_Node node; unsigned long diffaccum; wkcolor error; } Gif_LossyNode; typedef struct Gif_CodeTable { Gif_Node** links; int clear_code; unsigned max_loss; uint32_t maxpos; union { Gif_Node* nl; Gif_LossyNode* ls; } nodes; int links_pos; kcolor* global_kcs; kcolor* local_kcs; kcolor* kcs; double loss_scale; unsigned* kcdiff; } Gif_CodeTable; struct Gif_Writer { FILE *f; uint8_t *v; uint32_t pos; uint32_t cap; Gif_CompressInfo gcinfo; int global_size; int local_size; int errors; int cleared; Gif_CodeTable code_table; void (*byte_putter)(uint8_t, struct Gif_Writer *); void (*block_putter)(const uint8_t *, size_t, struct Gif_Writer *); }; #define gifputbyte(b, grr) ((*grr->byte_putter)(b, grr)) #define gifputblock(b, l, grr) ((*grr->block_putter)(b, l, grr)) static inline void gifputunsigned(uint16_t uns, Gif_Writer *grr) { gifputbyte(uns & 0xFF, grr); gifputbyte(uns >> 8, grr); } static void file_byte_putter(uint8_t b, Gif_Writer *grr) { fputc(b, grr->f); } static void file_block_putter(const uint8_t *block, size_t size, Gif_Writer *grr) { if (fwrite(block, 1, size, grr->f) != size) grr->errors = 1; } static void memory_byte_putter(uint8_t b, Gif_Writer *grr) { if (grr->pos >= grr->cap) { grr->cap = (grr->cap ? grr->cap * 2 : 1024); Gif_ReArray(grr->v, uint8_t, grr->cap); } if (grr->v) { grr->v[grr->pos] = b; grr->pos++; } } static void memory_block_putter(const uint8_t *data, size_t len, Gif_Writer *grr) { while (grr->pos + len >= grr->cap) { grr->cap = (grr->cap ? grr->cap * 2 : 1024); Gif_ReArray(grr->v, uint8_t, grr->cap); } if (grr->v) { memcpy(grr->v + grr->pos, data, len); grr->pos += len; } } static inline int gfc_init(Gif_CodeTable* gfc, const Gif_CompressInfo* gcinfo) { gfc->max_loss = 0; if (gcinfo->loss > 0) gfc->max_loss = gcinfo->loss * kc_distance(kc_make8g(0, 0, 0), kc_make8g(0, 0, 8)); if (gfc->max_loss > 0) { gfc->loss_scale = 4.0 / gfc->max_loss; gfc->nodes.ls = Gif_NewArray(Gif_LossyNode, NODES_SIZE); } else gfc->nodes.nl = Gif_NewArray(Gif_Node, NODES_SIZE); gfc->links = Gif_NewArray(Gif_Node*, LINKS_SIZE); gfc->global_kcs = NULL; gfc->local_kcs = NULL; gfc->kcs = NULL; gfc->kcdiff = NULL; return (gfc->max_loss > 0 ? !!gfc->nodes.ls : !!gfc->nodes.nl) && gfc->links; } static int gif_writer_init(Gif_Writer* grr, FILE* f, const Gif_CompressInfo* gcinfo) { grr->f = f; grr->v = NULL; grr->pos = grr->cap = 0; if (gcinfo) grr->gcinfo = *gcinfo; else Gif_InitCompressInfo(&grr->gcinfo); grr->errors = 0; grr->cleared = 0; if (f) { grr->byte_putter = file_byte_putter; grr->block_putter = file_block_putter; } else { grr->byte_putter = memory_byte_putter; grr->block_putter = memory_block_putter; } return gfc_init(&grr->code_table, gcinfo); } static void gif_writer_cleanup(Gif_Writer* grr) { Gif_DeleteArray(grr->v); if (grr->code_table.max_loss > 0) Gif_DeleteArray(grr->code_table.nodes.ls); else Gif_DeleteArray(grr->code_table.nodes.nl); Gif_DeleteArray(grr->code_table.links); Gif_DeleteArray(grr->code_table.global_kcs); Gif_DeleteArray(grr->code_table.local_kcs); Gif_DeleteArray(grr->code_table.kcdiff); } static inline void gfc_clear(Gif_CodeTable *gfc) { int c; /* The first clear_code nodes are reserved for single-pixel codes */ gfc->links_pos = gfc->clear_code; for (c = 0; c < gfc->clear_code; c++) { Gif_Node* n = gfc->max_loss > 0 ? &gfc->nodes.ls[c].node : &gfc->nodes.nl[c]; n->code = c; n->type = LINKS_TYPE; n->suffix = c; n->length = 1; n->child.s = NULL; gfc->links[c] = n; } } static inline Gif_Node * gfc_lookup(Gif_CodeTable *gfc, Gif_Node *node, uint8_t suffix) { assert(suffix < gfc->clear_code); if (!node) return gfc->links[suffix]; if (node->type == TABLE_TYPE) return node->child.m[suffix]; for (node = node->child.s; node; node = node->sibling) if (node->suffix == suffix) return node; return NULL; } static void gfc_change_node_to_table(Gif_CodeTable *gfc, Gif_Node *work_node, Gif_Node *next_node) { /* change links node to table node */ Gif_Code c; Gif_Node **table = &gfc->links[gfc->links_pos]; Gif_Node *n; gfc->links_pos += gfc->clear_code; for (c = 0; c < gfc->clear_code; c++) table[c] = 0; table[next_node->suffix] = next_node; for (n = work_node->child.s; n; n = n->sibling) table[n->suffix] = n; work_node->type = TABLE_TYPE; work_node->child.m = table; } static inline void gfc_define(Gif_CodeTable* gfc, Gif_Node* work_node, uint8_t suffix, Gif_Code next_code) { /* Add a new code to our dictionary. First reserve a node for the added code. It's LINKS_TYPE at first. */ Gif_Node* next_node = gfc->max_loss > 0 ? &gfc->nodes.ls[next_code].node : &gfc->nodes.nl[next_code]; next_node->code = next_code; next_node->type = LINKS_TYPE; next_node->suffix = suffix; next_node->length = work_node->length + 1; next_node->child.s = NULL; /* link next_node into work_node's set of children */ if (work_node->type == TABLE_TYPE) work_node->child.m[suffix] = next_node; else if (work_node->type < MAX_LINKS_TYPE || gfc->links_pos + gfc->clear_code > LINKS_SIZE) { next_node->sibling = work_node->child.s; work_node->child.s = next_node; if (work_node->type < MAX_LINKS_TYPE) work_node->type++; } else gfc_change_node_to_table(gfc, work_node, next_node); } static inline const uint8_t * gif_imageline(Gif_Image *gfi, unsigned pos) { unsigned y, x; if (gfi->width == 0) return NULL; y = pos / gfi->width; x = pos - y * gfi->width; if (y == (unsigned) gfi->height) return NULL; else if (!gfi->interlace) return gfi->img[y] + x; else return gfi->img[Gif_InterlaceLine(y, gfi->height)] + x; } static inline unsigned gif_line_endpos(Gif_Image *gfi, unsigned pos) { unsigned y = pos / gfi->width; return (y + 1) * gfi->width; } static inline uint8_t gfc_pixel(Gif_CodeTable* gfc, Gif_Image* gfi, unsigned pos) { unsigned y = pos / gfi->width; unsigned x = pos - y * gfi->width; if (gfi->interlace) y = Gif_InterlaceLine(y, gfi->height); uint8_t p = gfi->img[y][x]; if (p >= gfc->clear_code) /* should not happen unless GIF_WRITE_CAREFUL_MIN_CODE_BITS */ p = 0; return p; } static int gfc_lossy_init(Gif_CodeTable* gfc, Gif_Stream* gfs, Gif_Image* gfi) { int i, j; Gif_Colormap* gfcm = gfi->local ? gfi->local : gfs->global; kcolor** kcs = gfi->local ? &gfc->local_kcs : &gfc->global_kcs; kcolor* old_kcs = gfc->kcs; if (!*kcs) { *kcs = Gif_NewArray(kcolor, 256); if (!*kcs) { gfc->max_loss = 0; return 0; } for (i = 0; i != gfcm->ncol; ++i) { (*kcs)[i] = kc_makegfcg(&gfcm->col[i]); } for (; i < 256; ++i) { (*kcs)[i] = kc_make8g(0, 0, 0); } } gfc->kcs = *kcs; if (gfc->kcdiff && kcs == &gfc->global_kcs && old_kcs == gfc->global_kcs) { return 1; } if (!gfc->kcdiff && !(gfc->kcdiff = Gif_NewArray(unsigned, 256 * 256))) { gfc->max_loss = 0; return 0; } for (i = 0; i != gfcm->ncol; ++i) { for (j = 0; j != i; ++j) { gfc->kcdiff[(i << 8) | j] = gfc->kcdiff[(j << 8) | i] = kc_distance(gfc->kcs[i], gfc->kcs[j]); } gfc->kcdiff[(i << 8) | i] = 0; } return 1; } /* difference between expected color a+dither and color b (used to calculate dithering required) */ static inline wkcolor diffused_difference(const Gif_CodeTable* gfc, int expected, int actual, wkcolor error) { int i; kcolor a = gfc->kcs[expected], b = gfc->kcs[actual]; wkcolor wkc; for (i = 0; i != 3; ++i) { wkc.a[i] = (int32_t) a.a[i] - (int32_t) b.a[i] + 3 * error.a[i] / 4; } return wkc; } static inline int gfc_lossy_candidate(Gif_CodeTable* gfc, Gif_Image* gfi, Gif_LossyNode* n, Gif_LossyNode* ch, uint8_t expected) { uint8_t actual = ch->node.suffix; unsigned diff; /* exact match */ if (expected == actual) { ch->diffaccum = n->diffaccum; ch->error = diffused_difference(gfc, expected, expected, n->error); return 1; } /* transparency never matches */ if (expected == gfi->transparent || actual == gfi->transparent) return 0; /* check local loss */ diff = gfc->kcdiff[(expected << 8) | actual]; if (diff > gfc->max_loss) return 0; /* check accumulated loss */ if (!wkc_iszero(n->error) && kc_distance(kc_adjust(gfc->kcs[expected], n->error), gfc->kcs[actual]) > gfc->max_loss) return 0; /* success */ ch->diffaccum = n->diffaccum + diff; ch->error = diffused_difference(gfc, expected, actual, n->error); return 1; } static Gif_Node* gfc_lookup_lossy(Gif_CodeTable* gfc, Gif_Image* gfi, unsigned pos) { Gif_LossyNode* best; double best_score; Gif_LossyNode* stack[NODES_SIZE]; unsigned nstack; if (pos == gfc->maxpos) return NULL; /* initialize with first node */ best = (Gif_LossyNode*) gfc_lookup(gfc, NULL, gfc_pixel(gfc, gfi, pos)); best->diffaccum = 0; best->error = wkc_zero(); best_score = 1.0; stack[0] = best; nstack = 1; /* explore until stack is empty */ while (nstack > 0) { Gif_LossyNode* n = stack[nstack - 1]; unsigned tpos = pos + n->node.length; int i; uint8_t expected; --nstack; /* check if this node is better than current best */ if (n != best) { double score = n->node.length - n->diffaccum * gfc->loss_scale; if (score > best_score) { best = n; best_score = score; } } /* reset error at start of line */ if (tpos == gfc->maxpos) continue; if (tpos % gfi->width == 0) n->error = wkc_zero(); expected = gfc_pixel(gfc, gfi, tpos); /* consider all children */ if (n->node.type == TABLE_TYPE) { for (i = 0; i < gfc->clear_code; ++i) { Gif_LossyNode* ch = (Gif_LossyNode*) n->node.child.m[i]; if (ch && gfc_lossy_candidate(gfc, gfi, n, ch, expected)) stack[nstack++] = ch; } } else { Gif_LossyNode* ch; for (ch = (Gif_LossyNode*) n->node.child.s; ch; ch = (Gif_LossyNode*) ch->node.sibling) { if (gfc_lossy_candidate(gfc, gfi, n, ch, expected)) stack[nstack++] = ch; } } } return &best->node; } static int write_compressed_data(Gif_Stream* gfs, Gif_Image* gfi, int min_code_bits, Gif_Writer* grr) { Gif_CodeTable* gfc = &grr->code_table; uint8_t stack_buffer[512 - 24]; uint8_t *buf = stack_buffer; unsigned bufpos = 0; unsigned bufcap = sizeof(stack_buffer) * 8; unsigned pos; unsigned clear_bufpos, clear_pos; unsigned line_endpos; const unsigned image_endpos = (size_t) gfi->height * (size_t) gfi->width; const uint8_t *imageline; unsigned run = 0; #define RUN_EWMA_SHIFT 4 #define RUN_EWMA_SCALE 19 #define RUN_INV_THRESH ((unsigned) (1 << RUN_EWMA_SCALE) / 3000) unsigned run_ewma = 0; Gif_Node *work_node; Gif_Node *next_node; Gif_Code next_code = 0; Gif_Code output_code; uint8_t suffix; int cur_code_bits; /* Here we go! */ gifputbyte(min_code_bits, grr); #define CUR_BUMP_CODE (1 << cur_code_bits) gfc->clear_code = (Gif_Code) (1 << min_code_bits); gfc->maxpos = gfi->width * gfi->height; grr->cleared = 0; cur_code_bits = min_code_bits + 1; /* next_code set by first runthrough of output clear_code */ GIF_DEBUG(("clear(%d) eoi(%d) bits(%d) ", gfc->clear_code, gfc->clear_code + 1, cur_code_bits)); work_node = NULL; output_code = gfc->clear_code; /* Because output_code is clear_code, we'll initialize next_code, et al. below. */ pos = clear_pos = clear_bufpos = 0; line_endpos = gfi->width; imageline = gif_imageline(gfi, pos); if (gfc->max_loss > 0) { gfc_lossy_init(gfc, gfs, gfi); } while (1) { /***** * Output 'output_code' to the memory buffer. */ if (bufpos + 32 >= bufcap) { unsigned ncap = bufcap * 2 + (24 << 3); uint8_t *nbuf = Gif_NewArray(uint8_t, ncap >> 3); if (!nbuf) goto error; memcpy(nbuf, buf, bufcap >> 3); if (buf != stack_buffer) Gif_DeleteArray(buf); buf = nbuf; bufcap = ncap; } { unsigned endpos = bufpos + cur_code_bits; do { if (bufpos & 7) buf[bufpos >> 3] |= output_code << (bufpos & 7); else if (bufpos & 0x7FF) buf[bufpos >> 3] = output_code >> (bufpos - endpos + cur_code_bits); else { buf[bufpos >> 3] = 255; endpos += 8; } bufpos += 8 - (bufpos & 7); } while (bufpos < endpos); bufpos = endpos; } /***** * Handle special codes. */ if (output_code == gfc->clear_code) { /* Clear data and prepare gfc */ cur_code_bits = min_code_bits + 1; next_code = gfc->clear_code + 2; run_ewma = 1 << RUN_EWMA_SCALE; run = 0; gfc_clear(gfc); clear_pos = clear_bufpos = 0; GIF_DEBUG(("clear ")); } else if (output_code == gfc->clear_code + 1) break; else { if (next_code > CUR_BUMP_CODE && cur_code_bits < GIF_MAX_CODE_BITS) /* bump up compression size */ ++cur_code_bits; /* Adjust current run length average. */ run = (run << RUN_EWMA_SCALE) + (1 << (RUN_EWMA_SHIFT - 1)); if (run < run_ewma) run_ewma -= (run_ewma - run) >> RUN_EWMA_SHIFT; else run_ewma += (run - run_ewma) >> RUN_EWMA_SHIFT; /* Reset run length. */ run = !!work_node; } /***** * Find the next code to output. */ if (gfc->max_loss) { work_node = gfc_lookup_lossy(gfc, gfi, pos); run = work_node ? work_node->length : 0; pos += run; if (pos < image_endpos) { /* Output the current code. */ if (next_code < GIF_MAX_CODE) { gfc_define(gfc, work_node, gfc_pixel(gfc, gfi, pos), next_code); next_code++; } else next_code = GIF_MAX_CODE + 1; /* to match "> CUR_BUMP_CODE" above */ /* Check whether to clear table. */ if (next_code > 4094) { int do_clear = grr->gcinfo.flags & GIF_WRITE_EAGER_CLEAR; if (!do_clear) { unsigned pixels_left = image_endpos - pos - 1; if (pixels_left) { /* Always clear if run_ewma gets small relative to min_code_bits. Otherwise, clear if #images/run is smaller than an empirical threshold, meaning it will take more than 3000 or so average runs to complete the image. */ if (run_ewma < ((36U << RUN_EWMA_SCALE) / min_code_bits) || pixels_left > UINT_MAX / RUN_INV_THRESH || run_ewma < pixels_left * RUN_INV_THRESH) do_clear = 1; } } if ((do_clear || run < 7) && !clear_pos) { clear_pos = pos - run; clear_bufpos = bufpos; } else if (!do_clear && run > 50) clear_pos = clear_bufpos = 0; if (do_clear) { GIF_DEBUG(("rewind %u pixels/%d bits", pos + 1 - clear_pos, bufpos + cur_code_bits - clear_bufpos)); output_code = gfc->clear_code; pos = clear_pos; bufpos = clear_bufpos; buf[bufpos >> 3] &= (1 << (bufpos & 7)) - 1; grr->cleared = 1; continue; } } /* Adjust current run length average. */ run = (run << RUN_EWMA_SCALE) + (1 << (RUN_EWMA_SHIFT - 1)); if (run < run_ewma) run_ewma -= (run_ewma - run) >> RUN_EWMA_SHIFT; else run_ewma += (run - run_ewma) >> RUN_EWMA_SHIFT; } output_code = work_node ? work_node->code : gfc->clear_code + 1; } else { /* If height is 0 -- no more pixels to write -- we output work_node next time around. */ while (imageline) { suffix = *imageline; if (suffix >= gfc->clear_code) /* should not happen unless GIF_WRITE_CAREFUL_MIN_CODE_BITS */ suffix = 0; next_node = gfc_lookup(gfc, work_node, suffix); imageline++; pos++; if (pos == line_endpos) { imageline = gif_imageline(gfi, pos); line_endpos += gfi->width; } if (next_node) { work_node = next_node; ++run; continue; } /* Output the current code. */ if (next_code < GIF_MAX_CODE) { gfc_define(gfc, work_node, suffix, next_code); next_code++; } else next_code = GIF_MAX_CODE + 1; /* to match "> CUR_BUMP_CODE" above */ /* Check whether to clear table. */ if (next_code > 4094) { int do_clear = grr->gcinfo.flags & GIF_WRITE_EAGER_CLEAR; if (!do_clear) { unsigned pixels_left = image_endpos - pos; if (pixels_left) { /* Always clear if run_ewma gets small relative to min_code_bits. Otherwise, clear if #images/run is smaller than an empirical threshold, meaning it will take more than 3000 or so average runs to complete the image. */ if (run_ewma < ((36U << RUN_EWMA_SCALE) / min_code_bits) || pixels_left > UINT_MAX / RUN_INV_THRESH || run_ewma < pixels_left * RUN_INV_THRESH) do_clear = 1; } } if ((do_clear || run < 7) && !clear_pos) { clear_pos = pos - (run + 1); clear_bufpos = bufpos; } else if (!do_clear && run > 50) clear_pos = clear_bufpos = 0; if (do_clear) { GIF_DEBUG(("rewind %u pixels/%d bits ", pos - clear_pos, bufpos + cur_code_bits - clear_bufpos)); output_code = gfc->clear_code; pos = clear_pos; imageline = gif_imageline(gfi, pos); line_endpos = gif_line_endpos(gfi, pos); bufpos = clear_bufpos; buf[bufpos >> 3] &= (1 << (bufpos & 7)) - 1; work_node = NULL; grr->cleared = 1; goto found_output_code; } } output_code = work_node->code; work_node = gfc->links[suffix]; goto found_output_code; } /* Ran out of data if we get here. */ output_code = (work_node ? work_node->code : gfc->clear_code + 1); work_node = NULL; found_output_code: ; } } /* Output memory buffer to stream. */ bufpos = (bufpos + 7) >> 3; buf[(bufpos - 1) & 0xFFFFFF00] = (bufpos - 1) & 0xFF; buf[bufpos] = 0; gifputblock(buf, bufpos + 1, grr); if (buf != stack_buffer) Gif_DeleteArray(buf); return 1; error: if (buf != stack_buffer) Gif_DeleteArray(buf); return 0; } static int calculate_min_code_bits(Gif_Image *gfi, const Gif_Writer* grr) { int colors_used = -1, min_code_bits, i; if (grr->gcinfo.flags & GIF_WRITE_CAREFUL_MIN_CODE_SIZE) { /* calculate m_c_b based on colormap */ if (grr->local_size > 0) colors_used = grr->local_size; else if (grr->global_size > 0) colors_used = grr->global_size; } else if (gfi->img) { /* calculate m_c_b from uncompressed data */ int x, y, width = gfi->width, height = gfi->height; colors_used = 0; for (y = 0; y < height && colors_used < 128; y++) { uint8_t *data = gfi->img[y]; for (x = width; x > 0; x--, data++) if (*data > colors_used) colors_used = *data; } colors_used++; } else if (gfi->compressed) { /* take m_c_b from compressed image */ colors_used = 1 << gfi->compressed[0]; } else { /* should never happen */ colors_used = 256; } min_code_bits = 2; /* min_code_bits of 1 isn't allowed */ i = 4; while (i < colors_used) { min_code_bits++; i *= 2; } return min_code_bits; } static int get_color_table_size(const Gif_Stream* gfs, Gif_Image* gfi, Gif_Writer* grr); static void save_compression_result(Gif_Image* gfi, Gif_Writer* grr, int ok) { if (!(grr->gcinfo.flags & GIF_WRITE_SHRINK) || (ok && (!gfi->compressed || gfi->compressed_len > grr->pos))) { if (gfi->compressed) (*gfi->free_compressed)((void *) gfi->compressed); if (ok) { gfi->compressed_len = grr->pos; gfi->compressed_errors = 0; gfi->compressed = grr->v; gfi->free_compressed = Gif_Free; grr->v = 0; grr->cap = 0; } else gfi->compressed = 0; } grr->pos = 0; } int Gif_FullCompressImage(Gif_Stream *gfs, Gif_Image *gfi, const Gif_CompressInfo *gcinfo) { int ok = 0; uint8_t min_code_bits; Gif_Writer grr; if (!gif_writer_init(&grr, NULL, gcinfo)) { if (!(grr.gcinfo.flags & GIF_WRITE_SHRINK)) Gif_ReleaseCompressedImage(gfi); goto done; } grr.global_size = get_color_table_size(gfs, 0, &grr); grr.local_size = get_color_table_size(gfs, gfi, &grr); min_code_bits = calculate_min_code_bits(gfi, &grr); ok = write_compressed_data(gfs, gfi, min_code_bits, &grr); save_compression_result(gfi, &grr, ok); if ((grr.gcinfo.flags & (GIF_WRITE_OPTIMIZE | GIF_WRITE_EAGER_CLEAR)) == GIF_WRITE_OPTIMIZE && grr.cleared && ok) { grr.gcinfo.flags |= GIF_WRITE_EAGER_CLEAR | GIF_WRITE_SHRINK; if (write_compressed_data(gfs, gfi, min_code_bits, &grr)) save_compression_result(gfi, &grr, 1); } done: gif_writer_cleanup(&grr); return ok; } static int get_color_table_size(const Gif_Stream* gfs, Gif_Image* gfi, Gif_Writer* grr) { Gif_Colormap *gfcm = gfi ? gfi->local : gfs->global; int ncol, totalcol, i; if (!gfcm || gfcm->ncol <= 0) return 0; /* Make sure ncol is reasonable */ ncol = gfcm->ncol; /* Possibly bump up 'ncol' based on 'transparent' values, if careful_min_code_bits */ if (grr->gcinfo.flags & GIF_WRITE_CAREFUL_MIN_CODE_SIZE) { if (gfi && gfi->transparent >= ncol) ncol = gfi->transparent + 1; else if (!gfi) for (i = 0; i < gfs->nimages; i++) if (gfs->images[i]->transparent >= ncol) ncol = gfs->images[i]->transparent + 1; } /* Make sure the colormap is a power of two entries! */ /* GIF format doesn't allow a colormap with only 1 entry. */ if (ncol > 256) ncol = 256; for (totalcol = 2; totalcol < ncol; totalcol *= 2) /* nada */; return totalcol; } static void write_color_table(Gif_Colormap* gfcm, int totalcol, Gif_Writer* grr) { Gif_Color *c = gfcm->col; int i, ncol = gfcm->ncol; for (i = 0; i < ncol && i < totalcol; i++, c++) { gifputbyte(c->gfc_red, grr); gifputbyte(c->gfc_green, grr); gifputbyte(c->gfc_blue, grr); } /* Pad out colormap with black. */ for (; i < totalcol; i++) { gifputbyte(0, grr); gifputbyte(0, grr); gifputbyte(0, grr); } } static int write_image(Gif_Stream* gfs, Gif_Image* gfi, Gif_Writer* grr) { uint8_t min_code_bits, packed = 0; grr->local_size = get_color_table_size(gfs, gfi, grr); gifputbyte(',', grr); gifputunsigned(gfi->left, grr); gifputunsigned(gfi->top, grr); gifputunsigned(gfi->width, grr); gifputunsigned(gfi->height, grr); if (grr->local_size > 0) { int size = 2; packed |= 0x80; while (size < grr->local_size) size *= 2, packed++; } if (gfi->interlace) packed |= 0x40; gifputbyte(packed, grr); if (grr->local_size > 0) write_color_table(gfi->local, grr->local_size, grr); /* calculate min_code_bits here (because calculation may involve recompression, if GIF_WRITE_CAREFUL_MIN_CODE_SIZE is true) */ min_code_bits = calculate_min_code_bits(gfi, grr); /* use existing compressed data if it exists. This will tend to whip people's asses who uncompress an image, keep the compressed data around, but modify the uncompressed data anyway. That sucks. */ if (gfi->compressed && (!(grr->gcinfo.flags & GIF_WRITE_CAREFUL_MIN_CODE_SIZE) || gfi->compressed[0] == min_code_bits)) { uint8_t *compressed = gfi->compressed; uint32_t compressed_len = gfi->compressed_len; while (compressed_len > 0) { uint16_t amt = (compressed_len > 0x7000 ? 0x7000 : compressed_len); gifputblock(compressed, amt, grr); compressed += amt; compressed_len -= amt; } } else if (!gfi->img) { Gif_UncompressImage(gfs, gfi); write_compressed_data(gfs, gfi, min_code_bits, grr); Gif_ReleaseUncompressedImage(gfi); } else write_compressed_data(gfs, gfi, min_code_bits, grr); return 1; } static void write_logical_screen_descriptor(Gif_Stream *gfs, Gif_Writer *grr) { uint8_t packed = 0x70; /* high resolution colors */ grr->global_size = get_color_table_size(gfs, 0, grr); Gif_CalculateScreenSize(gfs, 0); gifputunsigned(gfs->screen_width, grr); gifputunsigned(gfs->screen_height, grr); if (grr->global_size > 0) { uint16_t size = 2; packed |= 0x80; while (size < grr->global_size) size *= 2, packed++; } gifputbyte(packed, grr); if (gfs->background < grr->global_size) gifputbyte(gfs->background, grr); else gifputbyte(255, grr); gifputbyte(0, grr); /* no aspect ratio information */ if (grr->global_size > 0) write_color_table(gfs->global, grr->global_size, grr); } /* extension byte table: 0x01 plain text extension 0xCE name* 0xF9 graphic control extension 0xFE comment extension 0xFF application extension */ static void write_graphic_control_extension(Gif_Image *gfi, Gif_Writer *grr) { uint8_t packed = 0; gifputbyte('!', grr); gifputbyte(0xF9, grr); gifputbyte(4, grr); if (gfi->transparent >= 0) packed |= 0x01; packed |= (gfi->disposal & 0x07) << 2; gifputbyte(packed, grr); gifputunsigned(gfi->delay, grr); gifputbyte((uint8_t)gfi->transparent, grr); gifputbyte(0, grr); } static void blast_data(const uint8_t *data, int len, Gif_Writer *grr) { while (len > 0) { int s = len > 255 ? 255 : len; gifputbyte(s, grr); gifputblock(data, s, grr); data += s; len -= s; } gifputbyte(0, grr); } static void write_name_extension(char *id, Gif_Writer *grr) { gifputbyte('!', grr); gifputbyte(0xCE, grr); blast_data((uint8_t *)id, strlen(id), grr); } static void write_comment_extensions(Gif_Comment *gfcom, Gif_Writer *grr) { int i; for (i = 0; i < gfcom->count; i++) { gifputbyte('!', grr); gifputbyte(0xFE, grr); blast_data((const uint8_t *)gfcom->str[i], gfcom->len[i], grr); } } static void write_netscape_loop_extension(uint16_t value, Gif_Writer *grr) { gifputblock((const uint8_t *)"!\xFF\x0BNETSCAPE2.0\x03\x01", 16, grr); gifputunsigned(value, grr); gifputbyte(0, grr); } static void write_generic_extension(Gif_Extension *gfex, Gif_Writer *grr) { uint32_t pos = 0; if (gfex->kind < 0) return; /* ignore our private extensions */ gifputbyte('!', grr); gifputbyte(gfex->kind, grr); if (gfex->kind == 255) { /* an application extension */ if (gfex->applength) { gifputbyte(gfex->applength, grr); gifputblock((const uint8_t*) gfex->appname, gfex->applength, grr); } } if (gfex->packetized) gifputblock(gfex->data, gfex->length, grr); else { while (pos + 255 < gfex->length) { gifputbyte(255, grr); gifputblock(gfex->data + pos, 255, grr); pos += 255; } if (pos < gfex->length) { uint32_t len = gfex->length - pos; gifputbyte(len, grr); gifputblock(gfex->data + pos, len, grr); } } gifputbyte(0, grr); } static int write_gif(Gif_Stream *gfs, Gif_Writer *grr) { Gif_Extension* gfex; int ok = 0; int i; { uint8_t isgif89a = 0; if (gfs->end_comment || gfs->end_extension_list || gfs->loopcount > -1) isgif89a = 1; for (i = 0; i < gfs->nimages && !isgif89a; i++) { Gif_Image* gfi = gfs->images[i]; if (gfi->identifier || gfi->transparent != -1 || gfi->disposal || gfi->delay || gfi->comment || gfi->extension_list) isgif89a = 1; } if (isgif89a) gifputblock((const uint8_t *)"GIF89a", 6, grr); else gifputblock((const uint8_t *)"GIF87a", 6, grr); } write_logical_screen_descriptor(gfs, grr); if (gfs->loopcount > -1) write_netscape_loop_extension(gfs->loopcount, grr); for (i = 0; i < gfs->nimages; i++) if (!Gif_IncrementalWriteImage(grr, gfs, gfs->images[i])) goto done; for (gfex = gfs->end_extension_list; gfex; gfex = gfex->next) write_generic_extension(gfex, grr); if (gfs->end_comment) write_comment_extensions(gfs->end_comment, grr); gifputbyte(';', grr); ok = 1; done: return ok; } int Gif_FullWriteFile(Gif_Stream *gfs, const Gif_CompressInfo *gcinfo, FILE *f) { Gif_Writer grr; int ok = gif_writer_init(&grr, f, gcinfo) && write_gif(gfs, &grr); gif_writer_cleanup(&grr); return ok; } Gif_Writer* Gif_IncrementalWriteFileInit(Gif_Stream* gfs, const Gif_CompressInfo* gcinfo, FILE *f) { Gif_Writer* grr = Gif_New(Gif_Writer); if (!grr || !gif_writer_init(grr, f, gcinfo)) { Gif_Delete(grr); return NULL; } gifputblock((const uint8_t *)"GIF89a", 6, grr); write_logical_screen_descriptor(gfs, grr); if (gfs->loopcount > -1) write_netscape_loop_extension(gfs->loopcount, grr); return grr; } int Gif_IncrementalWriteImage(Gif_Writer* grr, Gif_Stream* gfs, Gif_Image* gfi) { Gif_Extension *gfex; for (gfex = gfi->extension_list; gfex; gfex = gfex->next) write_generic_extension(gfex, grr); if (gfi->comment) write_comment_extensions(gfi->comment, grr); if (gfi->identifier) write_name_extension(gfi->identifier, grr); if (gfi->transparent != -1 || gfi->disposal || gfi->delay) write_graphic_control_extension(gfi, grr); return write_image(gfs, gfi, grr); } int Gif_IncrementalWriteComplete(Gif_Writer* grr, Gif_Stream* gfs) { Gif_Extension* gfex; for (gfex = gfs->end_extension_list; gfex; gfex = gfex->next) write_generic_extension(gfex, grr); if (gfs->end_comment) write_comment_extensions(gfs->end_comment, grr); gifputbyte(';', grr); gif_writer_cleanup(grr); Gif_Delete(grr); return 1; } #undef Gif_CompressImage #undef Gif_WriteFile int Gif_CompressImage(Gif_Stream *gfs, Gif_Image *gfi) { return Gif_FullCompressImage(gfs, gfi, 0); } int Gif_WriteFile(Gif_Stream *gfs, FILE *f) { return Gif_FullWriteFile(gfs, 0, f); } #ifdef __cplusplus } #endif gifsicle-1.96/src/gifx.c000066400000000000000000000542031475770763400151560ustar00rootroot00000000000000/* gifx.c - Functions to turn GIFs in memory into X Pixmaps. Copyright (C) 1997-2025 Eddie Kohler, ekohler@gmail.com This file is part of the LCDF GIF library. The LCDF GIF library is free software. It is distributed under the GNU General Public License, version 2; you can copy, distribute, or alter it at will, as long as this notice is kept intact and this source code is made available. There is no warranty, express or implied. */ #if HAVE_CONFIG_H # include #endif #include #include #include #include #ifdef __cplusplus extern "C" { #endif #define SAFELS(a,b) ((b) < 0 ? (a) >> -(b) : (a) << (b)) struct Gif_XColormap { Gif_XContext *x_context; Gif_Colormap *colormap; int allocated; int claimed; uint16_t npixels; unsigned long *pixels; Gif_XColormap *next; }; static unsigned long crap_pixels[256]; static void load_closest(Gif_XContext *gfx) { XColor *color; uint16_t ncolor; uint16_t ncolormap; int i; if (gfx->closest) return; ncolormap = ncolor = gfx->ncolormap; if (ncolor > 256) ncolor = 256; color = Gif_NewArray(XColor, ncolor); if (ncolormap > 256) for (i = 0; i < ncolor; i++) color[i].pixel = (rand() >> 4) % ncolormap; else for (i = 0; i < ncolor; i++) color[i].pixel = i; XQueryColors(gfx->display, gfx->colormap, color, ncolor); gfx->closest = Gif_NewArray(Gif_Color, ncolor); for (i = 0; i < ncolor; i++) { Gif_Color *c = &gfx->closest[i]; c->haspixel = 1; c->gfc_red = color[i].red >> 8; c->gfc_green = color[i].green >> 8; c->gfc_blue = color[i].blue >> 8; c->pixel = color[i].pixel; } gfx->nclosest = ncolor; Gif_DeleteArray(color); } static unsigned long allocate_closest(Gif_XContext *gfx, Gif_Color *c) { Gif_Color *closer; Gif_Color *got = 0; uint32_t distance = 0x4000000; int i; load_closest(gfx); for (i = 0, closer = gfx->closest; i < gfx->nclosest; i++, closer++) { int redd = c->gfc_red - closer->gfc_red; int greend = c->gfc_green - closer->gfc_green; int blued = c->gfc_blue - closer->gfc_blue; uint32_t d = redd * redd + greend * greend + blued * blued; if (d < distance) { distance = d; got = closer; } } if (!got) return 0; if (!got->haspixel) { XColor xcol; xcol.red = got->gfc_red | (got->gfc_red << 8); xcol.green = got->gfc_green | (got->gfc_green << 8); xcol.blue = got->gfc_blue | (got->gfc_blue << 8); if (XAllocColor(gfx->display, gfx->colormap, &xcol) == 0) { /* Probably was a read/write color cell. Get rid of it!! */ *got = gfx->closest[gfx->nclosest - 1]; gfx->nclosest--; return allocate_closest(gfx, c); } got->pixel = xcol.pixel; got->haspixel = 1; } return got->pixel; } static void allocate_colors(Gif_XColormap *gfxc) { Gif_XContext *gfx = gfxc->x_context; uint16_t size = gfxc->colormap->ncol; Gif_Color *c = gfxc->colormap->col; unsigned long *pixels = gfxc->pixels; XColor xcol; int i; if (!gfxc->allocated) { if (size > gfxc->npixels) size = gfxc->npixels; for (i = 0; i < size; i++, c++) { xcol.red = c->gfc_red | (c->gfc_red << 8); xcol.green = c->gfc_green | (c->gfc_green << 8); xcol.blue = c->gfc_blue | (c->gfc_blue << 8); if (XAllocColor(gfx->display, gfx->colormap, &xcol)) pixels[i] = xcol.pixel; else pixels[i] = allocate_closest(gfx, c); } gfxc->allocated = 1; gfxc->claimed = 0; } } static void deallocate_colors(Gif_XColormap *gfxc) { Gif_XContext *gfx = gfxc->x_context; if (gfxc->allocated && !gfxc->claimed) { XFreeColors(gfx->display, gfx->colormap, gfxc->pixels, gfxc->npixels, 0); gfxc->allocated = 0; } } static Gif_XColormap * create_x_colormap_extension(Gif_XContext *gfx, Gif_Colormap *gfcm) { Gif_XColormap *gfxc; unsigned long *pixels; if (!gfcm) return 0; gfxc = Gif_New(Gif_XColormap); pixels = gfxc ? Gif_NewArray(unsigned long, 256) : 0; if (pixels) { gfxc->x_context = gfx; gfxc->colormap = gfcm; gfxc->allocated = 0; gfxc->npixels = gfcm->ncol; gfxc->pixels = pixels; gfxc->next = gfx->xcolormap; gfx->xcolormap = gfxc; return gfxc; } else { Gif_Delete(gfxc); Gif_DeleteArray(pixels); return 0; } } static Gif_XColormap * find_x_colormap_extension(Gif_XContext *gfx, Gif_Colormap *gfcm, int create) { Gif_XColormap *gfxc = gfx->xcolormap; if (!gfcm) return 0; while (gfxc) { if (gfxc->colormap == gfcm) return gfxc; gfxc = gfxc->next; } if (create) return create_x_colormap_extension(gfx, gfcm); else return 0; } int Gif_XAllocateColors(Gif_XContext *gfx, Gif_Colormap *gfcm) { Gif_XColormap *gfxc = find_x_colormap_extension(gfx, gfcm, 1); if (gfxc) { allocate_colors(gfxc); return 1; } else return 0; } void Gif_XDeallocateColors(Gif_XContext *gfx, Gif_Colormap *gfcm) { Gif_XColormap *gfxc = find_x_colormap_extension(gfx, gfcm, 0); if (gfxc) deallocate_colors(gfxc); } unsigned long * Gif_XClaimStreamColors(Gif_XContext *gfx, Gif_Stream *gfs, int *np_store) { int i; int npixels = 0; unsigned long *pixels; Gif_Colormap *global = gfs->global; *np_store = 0; for (i = 0; i < gfs->nimages; i++) { Gif_Image *gfi = gfs->images[i]; Gif_Colormap *gfcm = (gfi->local ? gfi->local : global); Gif_XColormap *gfxc = find_x_colormap_extension(gfx, gfcm, 0); if (gfxc && gfxc->allocated && gfxc->claimed == 0) { gfxc->claimed = 2; npixels += gfxc->npixels; if (gfcm == global) global = 0; } } if (!npixels) return 0; pixels = Gif_NewArray(unsigned long, npixels); if (!pixels) return 0; *np_store = npixels; npixels = 0; global = gfs->global; for (i = 0; i < gfs->nimages; i++) { Gif_Image *gfi = gfs->images[i]; Gif_Colormap *gfcm = (gfi->local ? gfi->local : global); Gif_XColormap *gfxc = find_x_colormap_extension(gfx, gfcm, 0); if (gfxc && gfxc->allocated && gfxc->claimed == 2) { memcpy(pixels + npixels, gfxc->pixels, gfxc->npixels); npixels += gfxc->npixels; gfxc->claimed = 1; if (gfcm == global) global = 0; } } return pixels; } /* Getting pixmaps */ #define BYTESIZE 8 static int put_sub_image_colormap(Gif_XContext *gfx, Gif_Stream* gfs, Gif_Image *gfi, Gif_Colormap *gfcm, int left, int top, int width, int height, Pixmap pixmap, int pixmap_x, int pixmap_y) { XImage *ximage; uint8_t *xdata; int i, j, k; size_t bytes_per_line; unsigned long saved_transparent = 0; int release_uncompressed = 0; uint16_t nct; unsigned long *pixels; /* Find the correct image and colormap */ if (!gfi) return 0; if (!gfx->image_gc) gfx->image_gc = XCreateGC(gfx->display, pixmap, 0, 0); if (!gfx->image_gc) return 0; /* Make sure the image is uncompressed */ if (!gfi->img && !gfi->image_data && gfi->compressed) { Gif_UncompressImage(gfs, gfi); release_uncompressed = 1; } /* Check subimage dimensions */ if (width <= 0 || height <= 0 || left < 0 || top < 0 || left+width <= 0 || top+height <= 0 || left+width > gfi->width || top+height > gfi->height) return 0; /* Allocate colors from the colormap; make sure the transparent color * has the given pixel value */ if (gfcm) { Gif_XColormap *gfxc = find_x_colormap_extension(gfx, gfcm, 1); if (!gfxc) return 0; allocate_colors(gfxc); pixels = gfxc->pixels; nct = gfxc->npixels; } else { for (i = 0; i < 256; i++) crap_pixels[i] = gfx->foreground_pixel; pixels = crap_pixels; nct = 256; } if (gfi->transparent >= 0 && gfi->transparent < 256) { saved_transparent = pixels[ gfi->transparent ]; pixels[ gfi->transparent ] = gfx->transparent_pixel; } /* Set up the X image */ if (gfx->depth <= 8) i = 8; else if (gfx->depth <= 16) i = 16; else i = 32; ximage = XCreateImage(gfx->display, gfx->visual, gfx->depth, gfx->depth == 1 ? XYBitmap : ZPixmap, 0, NULL, width, height, i, 0); ximage->bitmap_bit_order = ximage->byte_order = LSBFirst; bytes_per_line = ximage->bytes_per_line; xdata = Gif_NewArray(uint8_t, bytes_per_line * height); ximage->data = (char *)xdata; /* The main loop */ if (ximage->bits_per_pixel % 8 == 0) { /* Optimize for cases where a pixel is exactly one or more bytes */ int bytes_per_pixel = ximage->bits_per_pixel / 8; for (j = 0; j < height; j++) { uint8_t *line = gfi->img[top + j] + left; uint8_t *writer = xdata + bytes_per_line * j; for (i = 0; i < width; i++) { unsigned long pixel; if (line[i] < nct) pixel = pixels[line[i]]; else pixel = pixels[0]; for (k = 0; k < bytes_per_pixel; k++) { *writer++ = pixel; pixel >>= 8; } } } } else { /* Other bits-per-pixel */ int bits_per_pixel = ximage->bits_per_pixel; uint32_t bits_per_pixel_mask = (1UL << bits_per_pixel) - 1; for (j = 0; j < height; j++) { int imshift = 0; uint32_t impixel = 0; uint8_t *line = gfi->img[top + j] + left; uint8_t *writer = xdata + bytes_per_line * j; for (i = 0; i < width; i++) { unsigned long pixel; if (line[i] < nct) pixel = pixels[line[i]]; else pixel = pixels[0]; impixel |= SAFELS(pixel & bits_per_pixel_mask, imshift); while (imshift + bits_per_pixel >= BYTESIZE) { *writer++ = impixel; imshift -= BYTESIZE; impixel = SAFELS(pixel, imshift); } imshift += bits_per_pixel; } if (imshift) *writer++ = impixel; } } /* Restore saved transparent pixel value */ if (gfi->transparent >= 0 && gfi->transparent < 256) pixels[ gfi->transparent ] = saved_transparent; /* Put it onto the pixmap */ XPutImage(gfx->display, pixmap, gfx->image_gc, ximage, 0, 0, pixmap_x, pixmap_y, width, height); Gif_DeleteArray(xdata); ximage->data = 0; /* avoid freeing it again in XDestroyImage */ XDestroyImage(ximage); if (release_uncompressed) Gif_ReleaseUncompressedImage(gfi); return 1; } Pixmap Gif_XSubImageColormap(Gif_XContext *gfx, Gif_Stream* gfs, Gif_Image *gfi, Gif_Colormap *gfcm, int left, int top, int width, int height) { Pixmap pixmap = XCreatePixmap(gfx->display, gfx->drawable, width ? width : 1, height ? height : 1, gfx->depth); if (pixmap) { if (put_sub_image_colormap(gfx, gfs, gfi, gfcm, left, top, width, height, pixmap, 0, 0)) return pixmap; else XFreePixmap(gfx->display, pixmap); } return None; } Pixmap Gif_XImage(Gif_XContext *gfx, Gif_Stream *gfs, Gif_Image *gfi) { Gif_Colormap *gfcm; if (!gfi && gfs->nimages) gfi = gfs->images[0]; if (!gfi) return None; gfcm = gfi->local; if (!gfcm) gfcm = gfs->global; return Gif_XSubImageColormap(gfx, gfs, gfi, gfcm, 0, 0, gfi->width, gfi->height); } Pixmap Gif_XImageColormap(Gif_XContext *gfx, Gif_Stream *gfs, Gif_Colormap *gfcm, Gif_Image *gfi) { if (!gfi && gfs->nimages) gfi = gfs->images[0]; if (!gfi) return None; return Gif_XSubImageColormap(gfx, gfs, gfi, gfcm, 0, 0, gfi->width, gfi->height); } Pixmap Gif_XSubImage(Gif_XContext *gfx, Gif_Stream *gfs, Gif_Image *gfi, int left, int top, int width, int height) { Gif_Colormap *gfcm; if (!gfi && gfs->nimages) gfi = gfs->images[0]; if (!gfi) return None; gfcm = gfi->local; if (!gfcm) gfcm = gfs->global; return Gif_XSubImageColormap(gfx, gfs, gfi, gfcm, left, top, width, height); } Pixmap Gif_XSubMask(Gif_XContext* gfx, Gif_Stream* gfs, Gif_Image* gfi, int left, int top, int width, int height) { Pixmap pixmap = None; XImage *ximage; uint8_t *xdata; int i, j; int transparent; size_t bytes_per_line; int release_uncompressed = 0; /* Find the correct image */ if (!gfi) return None; /* Check subimage dimensions */ if (width <= 0 || height <= 0 || left < 0 || top < 0 || left+width <= 0 || top+height <= 0 || left+width > gfi->width || top+height > gfi->height) return None; /* Make sure the image is uncompressed */ if (!gfi->img && !gfi->image_data && gfi->compressed) { Gif_UncompressImage(gfs, gfi); release_uncompressed = 1; } /* Create the X image */ ximage = XCreateImage(gfx->display, gfx->visual, 1, XYBitmap, 0, NULL, width, height, 8, 0); ximage->bitmap_bit_order = ximage->byte_order = LSBFirst; bytes_per_line = ximage->bytes_per_line; xdata = Gif_NewArray(uint8_t, bytes_per_line * height); ximage->data = (char *)xdata; transparent = gfi->transparent; /* The main loop */ for (j = 0; j < height; j++) { int imshift = 0; uint32_t impixel = 0; uint8_t *line = gfi->img[top + j] + left; uint8_t *writer = xdata + bytes_per_line * j; for (i = 0; i < width; i++) { if (line[i] == transparent) impixel |= 1 << imshift; if (++imshift >= BYTESIZE) { *writer++ = impixel; imshift = 0; impixel = 0; } } if (imshift) *writer++ = impixel; } /* Create the pixmap */ pixmap = XCreatePixmap(gfx->display, gfx->drawable, width ? width : 1, height ? height : 1, 1); if (!gfx->mask_gc) gfx->mask_gc = XCreateGC(gfx->display, pixmap, 0, 0); if (pixmap && gfx->mask_gc) XPutImage(gfx->display, pixmap, gfx->mask_gc, ximage, 0, 0, 0, 0, width, height); Gif_DeleteArray(xdata); ximage->data = 0; /* avoid freeing it again in XDestroyImage */ XDestroyImage(ximage); if (release_uncompressed) Gif_ReleaseUncompressedImage(gfi); return pixmap; } Pixmap Gif_XMask(Gif_XContext *gfx, Gif_Stream *gfs, Gif_Image *gfi) { if (!gfi && gfs->nimages) gfi = gfs->images[0]; if (!gfi) return None; return Gif_XSubMask(gfx, gfs, gfi, 0, 0, gfi->width, gfi->height); } static Pixmap screen_pixmap(Gif_XContext *gfx, Gif_Stream *gfs) { return XCreatePixmap(gfx->display, gfx->drawable, gfs->screen_width, gfs->screen_height, gfx->depth); } static int apply_background(Gif_XContext *gfx, Gif_Stream *gfs, int i, Pixmap pixmap) { Gif_Image *gfi = gfs->images[i >= 0 ? i : 0]; Gif_Colormap *gfcm = (gfi->local ? gfi->local : gfs->global); unsigned long bg_pixel; /* find bg_pixel */ if (gfs->global && gfs->background < gfs->global->ncol && gfs->images[0]->transparent < 0) { Gif_XColormap *gfxc = find_x_colormap_extension(gfx, gfcm, 1); if (!gfxc) return -1; allocate_colors(gfxc); bg_pixel = gfxc->pixels[gfs->background]; } else bg_pixel = gfx->transparent_pixel; /* install it as the foreground color on gfx->image_gc */ if (!gfx->image_gc) gfx->image_gc = XCreateGC(gfx->display, pixmap, 0, 0); if (!gfx->image_gc) return -1; XSetForeground(gfx->display, gfx->image_gc, bg_pixel); gfx->transparent_pixel = bg_pixel; /* clear the image portion */ if (i < 0) XFillRectangle(gfx->display, pixmap, gfx->image_gc, 0, 0, gfs->screen_width, gfs->screen_height); else /*if (gfi->transparent < 0)*/ XFillRectangle(gfx->display, pixmap, gfx->image_gc, gfi->left, gfi->top, gfi->width, gfi->height); /*else { Pixmap mask = Gif_XMask(gfx, gfs, gfi); if (mask == None) return -1; XSetClipMask(gfx->display, gfx->image_gc, mask); XSetClipOrigin(gfx->display, gfx->image_gc, gfi->left, gfi->top); XFillRectangle(gfx->display, pixmap, gfx->image_gc, gfi->left, gfi->top, gfi->width, gfi->height); XSetClipMask(gfx->display, gfx->image_gc, None); XFreePixmap(gfx->display, mask); }*/ return 0; } static int apply_image(Gif_XContext *gfx, Gif_Stream *gfs, Gif_Image *gfi, Pixmap pixmap) { Pixmap image = Gif_XImage(gfx, gfs, gfi), mask; if (image == None) return -1; if (gfi->transparent >= 0) { mask = Gif_XMask(gfx, gfs, gfi); if (mask == None) { XFreePixmap(gfx->display, image); return -1; } XSetClipMask(gfx->display, gfx->image_gc, mask); XSetClipOrigin(gfx->display, gfx->image_gc, gfi->left, gfi->top); XCopyArea(gfx->display, image, pixmap, gfx->image_gc, 0, 0, gfi->width, gfi->height, gfi->left, gfi->top); XSetClipMask(gfx->display, gfx->image_gc, None); XFreePixmap(gfx->display, mask); } else { XCopyArea(gfx->display, image, pixmap, gfx->image_gc, 0, 0, gfi->width, gfi->height, gfi->left, gfi->top); } XFreePixmap(gfx->display, image); return 0; } static int fullscreen(Gif_Stream *gfs, Gif_Image *gfi, int require_opaque) { return (gfi->left == 0 && gfi->top == 0 && gfi->width == gfs->screen_width && gfi->height == gfs->screen_height && (!require_opaque || gfi->transparent < 0)); } Gif_XFrame * Gif_NewXFrames(Gif_Stream *gfs) { int i, last_postdisposal = -1; Gif_XFrame *fs = Gif_NewArray(Gif_XFrame, gfs->nimages); if (!fs) return 0; for (i = 0; i < gfs->nimages; ++i) { Gif_Image *gfi = gfs->images[i]; fs[i].pixmap = None; if (gfi->disposal == GIF_DISPOSAL_PREVIOUS) fs[i].postdisposal = last_postdisposal; else fs[i].postdisposal = i; last_postdisposal = fs[i].postdisposal; } return fs; } void Gif_DeleteXFrames(Gif_XContext *gfx, Gif_Stream *gfs, Gif_XFrame *fs) { int i; for (i = 0; i < gfs->nimages; ++i) if (fs[i].pixmap) XFreePixmap(gfx->display, fs[i].pixmap); Gif_DeleteArray(fs); } Pixmap Gif_XNextImage(Gif_XContext *gfx, Gif_Stream *gfs, int i, Gif_XFrame *frames) { Pixmap result = None; unsigned long old_transparent = gfx->transparent_pixel; Gif_Image *gfi; int previ, scani; /* return already rendered pixmap if any */ if (frames[i].pixmap != None) return frames[i].pixmap; /* render fullscreen image */ gfi = gfs->images[i]; if (fullscreen(gfs, gfi, 1)) { frames[i].pixmap = Gif_XImage(gfx, gfs, gfi); return frames[i].pixmap; } /* image is not full screen, need to find background */ previ = i - 1; if (previ >= 0) previ = frames[previ].postdisposal; /* scan backwards for a renderable image */ scani = previ; while (scani >= 0 && frames[scani].pixmap == None && !fullscreen(gfs, gfs->images[scani], 1)) --scani; /* create the pixmap */ result = screen_pixmap(gfx, gfs); if (result == None) return None; /* scan forward to produce background */ gfi = (scani >= 0 ? gfs->images[scani] : 0); if (gfi && (gfi->disposal != GIF_DISPOSAL_BACKGROUND || !fullscreen(gfs, gfi, 1))) { /* perhaps we need to create an image (if so, must be fullscreen) */ if (frames[scani].pixmap == None) { frames[scani].pixmap = Gif_XImage(gfx, gfs, gfi); if (frames[scani].pixmap == None) goto error_exit; } XCopyArea(gfx->display, frames[scani].pixmap, result, gfx->image_gc, 0, 0, gfs->screen_width, gfs->screen_height, 0, 0); } if (!gfi || gfi->disposal == GIF_DISPOSAL_BACKGROUND) { if (apply_background(gfx, gfs, scani, result) < 0) goto error_exit; } while (scani < previ) { ++scani; gfi = gfs->images[scani]; if (gfi->disposal == GIF_DISPOSAL_BACKGROUND) { if (apply_background(gfx, gfs, scani, result) < 0) goto error_exit; } else if (gfi->disposal != GIF_DISPOSAL_PREVIOUS) { if (apply_image(gfx, gfs, gfs->images[scani], result) < 0) goto error_exit; } } /* apply image */ if (gfs->screen_width != 0 && gfs->screen_height != 0) { if (apply_image(gfx, gfs, gfs->images[i], result) < 0) goto error_exit; } frames[i].pixmap = result; return frames[i].pixmap; error_exit: XFreePixmap(gfx->display, result); gfx->transparent_pixel = old_transparent; return None; } /** CREATING AND DESTROYING XCONTEXTS **/ static void delete_xcolormap(Gif_XColormap *gfxc) { Gif_XContext *gfx = gfxc->x_context; Gif_XColormap *prev = 0, *trav = gfx->xcolormap; while (trav != gfxc && trav) { prev = trav; trav = trav->next; } if (gfx->free_deleted_colormap_pixels) deallocate_colors(gfxc); if (prev) prev->next = gfxc->next; else gfx->xcolormap = gfxc->next; Gif_DeleteArray(gfxc->pixels); Gif_Delete(gfxc); } static void delete_colormap_hook(int dummy, void *colormap_x, void *callback_x) { Gif_Colormap *gfcm = (Gif_Colormap *)colormap_x; Gif_XContext *gfx = (Gif_XContext *)callback_x; Gif_XColormap *gfxc; (void) dummy; for (gfxc = gfx->xcolormap; gfxc; gfxc = gfxc->next) if (gfxc->colormap == gfcm) { delete_xcolormap(gfxc); return; } } Gif_XContext * Gif_NewXContextFromVisual(Display *display, int screen_number, Visual *visual, int depth, Colormap colormap) { Gif_XContext *gfx; gfx = Gif_New(Gif_XContext); gfx->display = display; gfx->screen_number = screen_number; gfx->drawable = RootWindow(display, screen_number); gfx->visual = visual; gfx->colormap = colormap; gfx->ncolormap = visual->map_entries; gfx->depth = depth; gfx->closest = 0; gfx->nclosest = 0; gfx->free_deleted_colormap_pixels = 0; gfx->xcolormap = 0; gfx->image_gc = None; gfx->mask_gc = None; gfx->transparent_pixel = 0UL; gfx->foreground_pixel = 1UL; gfx->refcount = 0; Gif_AddDeletionHook(GIF_T_COLORMAP, delete_colormap_hook, gfx); return gfx; } Gif_XContext * Gif_NewXContext(Display *display, Window window) { XWindowAttributes attr; XGetWindowAttributes(display, window, &attr); return Gif_NewXContextFromVisual(display, XScreenNumberOfScreen(attr.screen), attr.visual, attr.depth, attr.colormap); } void Gif_DeleteXContext(Gif_XContext *gfx) { if (!gfx) return; if (--gfx->refcount > 0) return; while (gfx->xcolormap) delete_xcolormap(gfx->xcolormap); if (gfx->image_gc) XFreeGC(gfx->display, gfx->image_gc); if (gfx->mask_gc) XFreeGC(gfx->display, gfx->mask_gc); Gif_DeleteArray(gfx->closest); Gif_RemoveDeletionHook(GIF_T_COLORMAP, delete_colormap_hook, gfx); Gif_Delete(gfx); } #ifdef __cplusplus } #endif gifsicle-1.96/src/kcolor.c000066400000000000000000000621241475770763400155130ustar00rootroot00000000000000/* kcolor.c - Kcolor subsystem for gifsicle. Copyright (C) 1997-2025 Eddie Kohler, ekohler@gmail.com This file is part of gifsicle. Gifsicle is free software. It is distributed under the GNU Public License, version 2; you can copy, distribute, or alter it at will, as long as this notice is kept intact and this source code is made available. There is no warranty, express or implied. */ #include #include "gifsicle.h" #include "kcolor.h" #include #include #include #include #include #include /* Invariant: (0<=x<256) ==> (srgb_revgamma[srgb_gamma[x] >> 7] <= x). */ static const uint16_t srgb_gamma_table_256[256] = { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 99, 110, 120, 132, 144, 157, 170, 184, 198, 213, 229, 246, 263, 281, 299, 319, 338, 359, 380, 403, 425, 449, 473, 498, 524, 551, 578, 606, 635, 665, 695, 727, 759, 792, 825, 860, 895, 931, 968, 1006, 1045, 1085, 1125, 1167, 1209, 1252, 1296, 1341, 1386, 1433, 1481, 1529, 1578, 1629, 1680, 1732, 1785, 1839, 1894, 1950, 2007, 2065, 2123, 2183, 2244, 2305, 2368, 2432, 2496, 2562, 2629, 2696, 2765, 2834, 2905, 2977, 3049, 3123, 3198, 3273, 3350, 3428, 3507, 3587, 3668, 3750, 3833, 3917, 4002, 4088, 4176, 4264, 4354, 4444, 4536, 4629, 4723, 4818, 4914, 5011, 5109, 5209, 5309, 5411, 5514, 5618, 5723, 5829, 5936, 6045, 6154, 6265, 6377, 6490, 6604, 6720, 6836, 6954, 7073, 7193, 7315, 7437, 7561, 7686, 7812, 7939, 8067, 8197, 8328, 8460, 8593, 8728, 8863, 9000, 9139, 9278, 9419, 9560, 9704, 9848, 9994, 10140, 10288, 10438, 10588, 10740, 10893, 11048, 11204, 11360, 11519, 11678, 11839, 12001, 12164, 12329, 12495, 12662, 12831, 13000, 13172, 13344, 13518, 13693, 13869, 14047, 14226, 14406, 14588, 14771, 14955, 15141, 15328, 15516, 15706, 15897, 16089, 16283, 16478, 16675, 16872, 17071, 17272, 17474, 17677, 17882, 18088, 18295, 18504, 18714, 18926, 19138, 19353, 19569, 19786, 20004, 20224, 20445, 20668, 20892, 21118, 21345, 21573, 21803, 22034, 22267, 22501, 22736, 22973, 23211, 23451, 23692, 23935, 24179, 24425, 24672, 24920, 25170, 25421, 25674, 25928, 26184, 26441, 26700, 26960, 27222, 27485, 27749, 28016, 28283, 28552, 28823, 29095, 29368, 29643, 29920, 30197, 30477, 30758, 31040, 31324, 31610, 31897, 32185, 32475, 32767 }; static const uint16_t srgb_revgamma_table_256[256] = { 0, 1628, 2776, 3619, 4309, 4904, 5434, 5914, 6355, 6765, 7150, 7513, 7856, 8184, 8497, 8798, 9086, 9365, 9634, 9895, 10147, 10393, 10631, 10864, 11091, 11312, 11528, 11739, 11946, 12148, 12347, 12541, 12732, 12920, 13104, 13285, 13463, 13639, 13811, 13981, 14149, 14314, 14476, 14637, 14795, 14951, 15105, 15257, 15408, 15556, 15703, 15848, 15991, 16133, 16273, 16412, 16549, 16685, 16819, 16953, 17084, 17215, 17344, 17472, 17599, 17725, 17849, 17973, 18095, 18217, 18337, 18457, 18575, 18692, 18809, 18925, 19039, 19153, 19266, 19378, 19489, 19600, 19710, 19819, 19927, 20034, 20141, 20247, 20352, 20457, 20560, 20664, 20766, 20868, 20969, 21070, 21170, 21269, 21368, 21466, 21564, 21661, 21758, 21854, 21949, 22044, 22138, 22232, 22326, 22418, 22511, 22603, 22694, 22785, 22875, 22965, 23055, 23144, 23232, 23321, 23408, 23496, 23583, 23669, 23755, 23841, 23926, 24011, 24095, 24180, 24263, 24347, 24430, 24512, 24595, 24676, 24758, 24839, 24920, 25001, 25081, 25161, 25240, 25319, 25398, 25477, 25555, 25633, 25710, 25788, 25865, 25941, 26018, 26094, 26170, 26245, 26321, 26396, 26470, 26545, 26619, 26693, 26766, 26840, 26913, 26986, 27058, 27130, 27202, 27274, 27346, 27417, 27488, 27559, 27630, 27700, 27770, 27840, 27910, 27979, 28048, 28117, 28186, 28255, 28323, 28391, 28459, 28527, 28594, 28661, 28728, 28795, 28862, 28928, 28995, 29061, 29127, 29192, 29258, 29323, 29388, 29453, 29518, 29582, 29646, 29711, 29775, 29838, 29902, 29965, 30029, 30092, 30155, 30217, 30280, 30342, 30404, 30466, 30528, 30590, 30652, 30713, 30774, 30835, 30896, 30957, 31017, 31078, 31138, 31198, 31258, 31318, 31378, 31437, 31497, 31556, 31615, 31674, 31733, 31791, 31850, 31908, 31966, 32024, 32082, 32140, 32198, 32255, 32313, 32370, 32427, 32484, 32541, 32598, 32654, 32711 }; static const float linear_srgb_table_256[256] = { 0.00000, 0.04984009, 0.08494473, 0.11070206, 0.13180381, 0.1500052, 0.1661857, 0.18085852, 0.19435316, 0.20689574, 0.21864912, 0.22973509, 0.2402475, 0.25026038, 0.25983337, 0.26901522, 0.27784654, 0.28636143, 0.29458886, 0.3025538, 0.31027776, 0.31777957, 0.32507575, 0.33218095, 0.33910814, 0.34586892, 0.35247374, 0.35893196, 0.3652521, 0.3714419, 0.37750843, 0.38345808, 0.38929683, 0.39503005, 0.40066284, 0.40619975, 0.41164514, 0.417003, 0.42227703, 0.42747074, 0.4325873, 0.4376298, 0.44260103, 0.4475037, 0.45234028, 0.45711315, 0.46182457, 0.4664766, 0.47107124, 0.4756104, 0.4800958, 0.4845292, 0.48891217, 0.49324623, 0.49753287, 0.5017734, 0.5059693, 0.5101216, 0.5142317, 0.5183006, 0.5223295, 0.5263194, 0.53027135, 0.53418624, 0.53806514, 0.54190874, 0.5457181, 0.54949385, 0.5532369, 0.556948, 0.5606278, 0.5642771, 0.56789654, 0.5714868, 0.57504845, 0.5785821, 0.5820884, 0.58556795, 0.58902115, 0.59244865, 0.59585094, 0.5992285, 0.60258186, 0.60591143, 0.60921764, 0.612501, 0.61576194, 0.6190008, 0.622218, 0.62541395, 0.62858903, 0.6317436, 0.63487804, 0.6379926, 0.6410878, 0.6441637, 0.64722085, 0.6502595, 0.6532799, 0.65628237, 0.65926725, 0.6622347, 0.6651851, 0.66811866, 0.67103565, 0.6739363, 0.67682093, 0.6796897, 0.6825429, 0.6853807, 0.6882034, 0.69101113, 0.69380414, 0.6965826, 0.69934684, 0.70209694, 0.7048331, 0.7075556, 0.7102645, 0.71296, 0.7156424, 0.7183118, 0.7209683, 0.7236121, 0.7262435, 0.7288625, 0.73146933, 0.73406404, 0.73664695, 0.73921806, 0.7417776, 0.74432564, 0.7468624, 0.749388, 0.75190246, 0.7544061, 0.7568989, 0.759381, 0.76185256, 0.7643137, 0.7667645, 0.7692052, 0.7716358, 0.7740564, 0.77646714, 0.77886814, 0.78125954, 0.78364134, 0.7860138, 0.7883768, 0.79073066, 0.7930754, 0.795411, 0.7977377, 0.80005556, 0.8023647, 0.8046651, 0.80695695, 0.8092403, 0.8115152, 0.8137818, 0.81604016, 0.8182903, 0.8205324, 0.8227665, 0.8249926, 0.8272109, 0.8294214, 0.8316242, 0.8338194, 0.836007, 0.8381871, 0.84035975, 0.84252506, 0.8446831, 0.84683394, 0.84897757, 0.85111415, 0.8532437, 0.85536623, 0.8574819, 0.8595907, 0.8616927, 0.86378807, 0.8658767, 0.8679587, 0.87003416, 0.87210315, 0.87416565, 0.8762218, 0.8782716, 0.8803151, 0.8823524, 0.8843835, 0.8864085, 0.8884274, 0.8904402, 0.8924471, 0.89444804, 0.8964431, 0.8984324, 0.9004158, 0.90239346, 0.9043654, 0.9063318, 0.9082925, 0.91024756, 0.9121972, 0.9141413, 0.91608, 0.9180133, 0.9199412, 0.92186373, 0.92378104, 0.9256931, 0.92759997, 0.92950165, 0.9313982, 0.93328965, 0.9351761, 0.9370575, 0.9389339, 0.9408054, 0.9426719, 0.9445336, 0.94639045, 0.9482424, 0.9500897, 0.9519322, 0.95377004, 0.9556032, 0.9574316, 0.9592555, 0.9610748, 0.96288955, 0.9646998, 0.9665055, 0.9683068, 0.9701037, 0.9718961, 0.9736842, 0.9754679, 0.97724736, 0.9790225, 0.9807934, 0.9825601, 0.98432255, 0.9860808, 0.987835, 0.989585, 0.9913309, 0.99307275, 0.9948106, 0.99654436, 0.99827415, 1.00000 }; const uint16_t* gamma_tables[2] = { srgb_gamma_table_256, srgb_revgamma_table_256 }; #if ENABLE_THREADS pthread_mutex_t kd3_sort_lock; #endif const char* kc_debug_str(kcolor x) { static int whichbuf = 0; static char buf[8][64]; whichbuf = (whichbuf + 1) % 8; if (x.a[0] >= 0 && x.a[1] >= 0 && x.a[2] >= 0) { x = kc_revgamma_transform(x); snprintf(buf[whichbuf], sizeof(buf[whichbuf]), "#%02X%02X%02X", x.a[0] >> 7, x.a[1] >> 7, x.a[2] >> 7); } else snprintf(buf[whichbuf], sizeof(buf[whichbuf]), "<%d,%d,%d>", x.a[0], x.a[1], x.a[2]); return buf[whichbuf]; } void kc_set_gamma(int type, double gamma) { static int cur_type = KC_GAMMA_SRGB; static double cur_gamma = 2.2; int i, j; uint16_t* g[2]; #if !HAVE_POW if (type == KC_GAMMA_NUMERIC && gamma != 1.0) { type = KC_GAMMA_SRGB; } #endif if (type == cur_type && (type != KC_GAMMA_NUMERIC || gamma == cur_gamma)) { return; } if (type != KC_GAMMA_NUMERIC) { if (gamma_tables[0] != srgb_gamma_table_256) { Gif_DeleteArray(gamma_tables[0]); } gamma_tables[0] = srgb_gamma_table_256; gamma_tables[1] = srgb_revgamma_table_256; } else { if (gamma_tables[0] == srgb_gamma_table_256) { gamma_tables[0] = Gif_NewArray(uint16_t, 512); gamma_tables[1] = gamma_tables[0] + 256; } g[0] = (uint16_t*) gamma_tables[0]; g[1] = (uint16_t*) gamma_tables[1]; for (j = 0; j != 256; ++j) { #if HAVE_POW g[0][j] = (int) (pow(j / 255.0, gamma) * 32767 + 0.5); g[1][j] = (int) (pow(j / 256.0, 1/gamma) * 32767 + 0.5); #else g[0][j] = (int) (j / 255.0 * 32767 + 0.5); g[1][j] = (int) (j / 256.0 * 32767 + 0.5); #endif /* The ++gamma_tables[][] ensures that round-trip gamma correction always preserve the input colors. Without it, one might have, for example, input values 0, 1, and 2 all mapping to gamma-corrected value 0. Then a round-trip through gamma correction loses information. */ for (i = j ? 0 : 2; i != 2; ++i) { while (g[i][j] <= g[i][j-1] && g[i][j] < 32767) ++g[i][j]; } } } cur_type = type; cur_gamma = gamma; } kcolor kc_revgamma_transform(kcolor x) { int d; for (d = 0; d != 3; ++d) { int c = gamma_tables[1][x.a[d] >> 7]; while (c < 0x7F80 && x.a[d] >= gamma_tables[0][(c + 0x80) >> 7]) c += 0x80; x.a[d] = c; } return x; } #if 0 static void kc_test_gamma() { int x, y, z; for (x = 0; x != 256; ++x) for (y = 0; y != 256; ++y) for (z = 0; z != 256; ++z) { kcolor k = kc_make8g(x, y, z); kc_revgamma_transform(&k); if ((k.a[0] >> 7) != x || (k.a[1] >> 7) != y || (k.a[2] >> 7) != z) { kcolor kg = kc_make8g(x, y, z); fprintf(stderr, "#%02X%02X%02X ->g #%04X%04X%04X ->revg #%02X%02X%02X!\n", x, y, z, kg.a[0], kg.a[1], kg.a[2], k.a[0] >> 7, k.a[1] >> 7, k.a[2] >> 7); assert(0); } } } #endif kcolor kc_oklab_transform(int a0, int a1, int a2) { #if HAVE_CBRTF float cr = linear_srgb_table_256[a0]; float cg = linear_srgb_table_256[a1]; float cb = linear_srgb_table_256[a2]; float l = 0.4122214708f * cr + 0.5363325363f * cg + 0.0514459929f * cb; float m = 0.2119034982f * cr + 0.6806995451f * cg + 0.1073969566f * cb; float s = 0.0883024619f * cr + 0.2817188376f * cg + 0.6299787005f * cb; float l_ = cbrtf(l); float m_ = cbrtf(m); float s_ = cbrtf(s); float okl = 0.2104542553f*l_ + 0.7936177850f*m_ - 0.0040720468f*s_; float oka = 1.9779984951f*l_ - 2.4285922050f*m_ + 0.4505937099f*s_; float okb = 0.0259040371f*l_ + 0.7827717662f*m_ - 0.8086757660f*s_; // On sRGB colors, `okl` ranges from 0 to 1, // `oka` ranges from -0.23388740 to +0.27621666, // `okb` ranges from -0.31152815 to +0.19856972. kcolor kc; kc.a[0] = okl * 32767; kc.a[1] = (oka + 0.5) * 32767; kc.a[2] = (okb + 0.5) * 32767; return kc; #else /* fall back to sRGB gamma */ return kc_make8g(a0, a1, a2); #endif } static int kchist_sizes[] = { 4093, 16381, 65521, 262139, 1048571, 4194301, 16777213, 67108859, 268435459, 1073741839 }; static void kchist_grow(kchist* kch); void kchist_init(kchist* kch) { int i; kch->h = Gif_NewArray(kchistitem, kchist_sizes[0]); kch->n = 0; kch->capacity = kchist_sizes[0]; for (i = 0; i != kch->capacity; ++i) kch->h[i].count = 0; } void kchist_cleanup(kchist* kch) { Gif_DeleteArray(kch->h); kch->h = NULL; } kchistitem* kchist_add(kchist* kch, kcolor k, kchist_count_t count) { unsigned hash1, hash2 = 0; kacolor ka; kchistitem *khi; ka.k = k; ka.a[3] = 0; if (!kch->capacity || kch->n > ((kch->capacity * 3) >> 4)) kchist_grow(kch); hash1 = (((ka.a[0] & 0x7FE0) << 15) | ((ka.a[1] & 0x7FE0) << 5) | ((ka.a[2] & 0x7FE0) >> 5)) % kch->capacity; while (1) { khi = &kch->h[hash1]; if (!khi->count || memcmp(&khi->ka, &ka, sizeof(ka)) == 0) break; if (!hash2) { hash2 = (((ka.a[0] & 0x03FF) << 20) | ((ka.a[1] & 0x03FF) << 10) | (ka.a[2] & 0x03FF)) % kch->capacity; hash2 = hash2 ? hash2 : 1; } hash1 += hash2; if (hash1 >= (unsigned) kch->capacity) hash1 -= kch->capacity; } if (!khi->count) { khi->ka = ka; ++kch->n; } khi->count += count; if (khi->count < count) khi->count = (kchist_count_t) -1; return khi; } static void kchist_grow(kchist* kch) { kchistitem* oldh = kch->h; int i, oldcapacity = kch->capacity ? kch->capacity : kch->n; for (i = 0; kchist_sizes[i] <= oldcapacity; ++i) /* do nothing */; kch->capacity = kchist_sizes[i]; kch->h = Gif_NewArray(kchistitem, kch->capacity); kch->n = 0; for (i = 0; i != kch->capacity; ++i) kch->h[i].count = 0; for (i = 0; i != oldcapacity; ++i) if (oldh[i].count) kchist_add(kch, oldh[i].ka.k, oldh[i].count); Gif_DeleteArray(oldh); } void kchist_compress(kchist* kch) { int i, j; for (i = 0, j = kch->n; i != kch->n; ) if (kch->h[i].count) ++i; else if (kch->h[j].count) { kch->h[i] = kch->h[j]; ++i, ++j; } else ++j; kch->capacity = 0; } void kchist_make(kchist* kch, Gif_Stream* gfs, uint32_t* ntransp_store) { uint32_t gcount[256], lcount[256]; uint32_t nbackground = 0, ntransparent = 0; int x, y, i, imagei; kchist_init(kch); for (i = 0; i != 256; ++i) gcount[i] = 0; /* Count pixels */ for (imagei = 0; imagei < gfs->nimages; ++imagei) { Gif_Image* gfi = gfs->images[imagei]; Gif_Colormap* gfcm = gfi->local ? gfi->local : gfs->global; uint32_t* count = gfi->local ? lcount : gcount; uint32_t old_transparent_count = 0; int only_compressed = (gfi->img == 0); if (!gfcm) continue; if (count == lcount) for (i = 0; i != 256; ++i) count[i] = 0; if (gfi->transparent >= 0) old_transparent_count = count[gfi->transparent]; /* unoptimize the image if necessary */ if (only_compressed) Gif_UncompressImage(gfs, gfi); /* sweep over the image data, counting pixels */ for (y = 0; y < gfi->height; ++y) { const uint8_t* data = gfi->img[y]; for (x = 0; x < gfi->width; ++x, ++data) ++count[*data]; } /* add counted colors to global histogram (local only) */ if (gfi->local) for (i = 0; i != gfcm->ncol; ++i) if (count[i] && i != gfi->transparent) kchist_add(kch, kc_makegfcg(&gfcm->col[i]), count[i]); if (gfi->transparent >= 0 && count[gfi->transparent] != old_transparent_count) { ntransparent += count[gfi->transparent] - old_transparent_count; count[gfi->transparent] = old_transparent_count; } /* if this image has background disposal, count its size towards the background's pixel count */ if (gfi->disposal == GIF_DISPOSAL_BACKGROUND) nbackground += (unsigned) gfi->width * (unsigned) gfi->height; /* throw out compressed image if necessary */ if (only_compressed) Gif_ReleaseUncompressedImage(gfi); } if (gfs->images[0]->transparent < 0 && gfs->global && gfs->background < gfs->global->ncol) gcount[gfs->background] += nbackground; else ntransparent += nbackground; if (gfs->global) for (i = 0; i != gfs->global->ncol; ++i) if (gcount[i]) kchist_add(kch, kc_makegfcg(&gfs->global->col[i]), gcount[i]); /* now, make the linear histogram from the hashed histogram */ kchist_compress(kch); *ntransp_store = ntransparent; } /***** * kd_tree allocation and deallocation **/ struct kd3_treepos { int pivot; int offset; }; void kd3_init(kd3_tree* kd3, kcolor (*transform)(int, int, int)) { kd3->tree = NULL; kd3->ks = Gif_NewArray(kcolor, 256); kd3->nitems = 0; kd3->items_cap = 256; kd3->transform = transform ? transform : kc_make8g; kd3->xradius = NULL; kd3->disabled = -1; } void kd3_cleanup(kd3_tree* kd3) { Gif_DeleteArray(kd3->tree); Gif_DeleteArray(kd3->ks); Gif_DeleteArray(kd3->xradius); } void kd3_add_transformed(kd3_tree* kd3, kcolor k) { if (kd3->nitems == kd3->items_cap) { kd3->items_cap *= 2; Gif_ReArray(kd3->ks, kcolor, kd3->items_cap); } kd3->ks[kd3->nitems] = k; ++kd3->nitems; if (kd3->tree) { Gif_DeleteArray(kd3->tree); Gif_DeleteArray(kd3->xradius); kd3->tree = NULL; kd3->xradius = NULL; } } static kd3_tree* kd3_sorter; static int kd3_item_compare_0(const void* a, const void* b) { const int* aa = (const int*) a, *bb = (const int*) b; return kd3_sorter->ks[*aa].a[0] - kd3_sorter->ks[*bb].a[0]; } static int kd3_item_compare_1(const void* a, const void* b) { const int* aa = (const int*) a, *bb = (const int*) b; return kd3_sorter->ks[*aa].a[1] - kd3_sorter->ks[*bb].a[1]; } static int kd3_item_compare_2(const void* a, const void* b) { const int* aa = (const int*) a, *bb = (const int*) b; return kd3_sorter->ks[*aa].a[2] - kd3_sorter->ks[*bb].a[2]; } static int (*kd3_item_compares[])(const void*, const void*) = { &kd3_item_compare_0, &kd3_item_compare_1, &kd3_item_compare_2 }; static int kd3_item_compare_all(const void* a, const void* b) { const int* aa = (const int*) a, *bb = (const int*) b; return memcmp(&kd3_sorter->ks[*aa], &kd3_sorter->ks[*bb], sizeof(kcolor)); } static int kd3_build_range(int* perm, int nperm, int n, int depth) { kd3_tree* kd3 = kd3_sorter; int m, nl, nr, aindex = depth % 3; if (depth > kd3->maxdepth) kd3->maxdepth = depth; while (n >= kd3->ntree) { kd3->ntree *= 2; Gif_ReArray(kd3->tree, kd3_treepos, kd3->ntree); } if (nperm <= 1) { kd3->tree[n].pivot = (nperm == 0 ? -1 : perm[0]); kd3->tree[n].offset = -1; return 2; } qsort(perm, nperm, sizeof(int), kd3_item_compares[aindex]); /* pick pivot: a color component to split */ m = nperm >> 1; while (m > 0 && kd3->ks[perm[m]].a[aindex] == kd3->ks[perm[m-1]].a[aindex]) --m; if (m == 0) { /* don't split entirely to the right (infinite loop) */ m = nperm >> 1; while (m < nperm - 1 /* also, don't split entirely to the left */ && kd3->ks[perm[m]].a[aindex] == kd3->ks[perm[m-1]].a[aindex]) ++m; } if (m == 0) kd3->tree[n].pivot = kd3->ks[perm[m]].a[aindex]; else kd3->tree[n].pivot = kd3->ks[perm[m-1]].a[aindex] + ((kd3->ks[perm[m]].a[aindex] - kd3->ks[perm[m-1]].a[aindex]) >> 1); /* recurse */ nl = kd3_build_range(perm, m, n+1, depth+1); kd3->tree[n].offset = 1+nl; nr = kd3_build_range(&perm[m], nperm - m, n+1+nl, depth+1); return 1+nl+nr; } #if 0 static void kd3_print_depth(kd3_tree* kd3, int depth, kd3_treepos* p, int* a, int* b) { int i; char x[6][10]; for (i = 0; i != 3; ++i) { if (a[i] == INT_MIN) snprintf(x[2*i], sizeof(x[2*i]), "*"); else snprintf(x[2*i], sizeof(x[2*i]), "%d", a[i]); if (b[i] == INT_MAX) snprintf(x[2*i+1], sizeof(x[2*i+1]), "*"); else snprintf(x[2*i+1], sizeof(x[2*i+1]), "%d", b[i]); } printf("%*s<%s:%s,%s:%s,%s:%s>", depth*3, "", x[0], x[1], x[2], x[3], x[4], x[5]); if (p->offset < 0) { if (p->pivot >= 0) { assert(kd3->ks[p->pivot].a[0] >= a[0]); assert(kd3->ks[p->pivot].a[1] >= a[1]); assert(kd3->ks[p->pivot].a[2] >= a[2]); assert(kd3->ks[p->pivot].a[0] < b[0]); assert(kd3->ks[p->pivot].a[1] < b[1]); assert(kd3->ks[p->pivot].a[2] < b[2]); printf(" ** @%d: <%d,%d,%d>\n", p->pivot, kd3->ks[p->pivot].a[0], kd3->ks[p->pivot].a[1], kd3->ks[p->pivot].a[2]); } } else { int aindex = depth % 3, x[3]; assert(p->pivot >= a[aindex]); assert(p->pivot < b[aindex]); printf((aindex == 0 ? " | <%d,_,_>\n" : aindex == 1 ? " | <_,%d,_>\n" : " | <_,_,%d>\n"), p->pivot); memcpy(x, b, sizeof(int) * 3); x[aindex] = p->pivot; kd3_print_depth(kd3, depth + 1, p + 1, a, x); memcpy(x, a, sizeof(int) * 3); x[aindex] = p->pivot; kd3_print_depth(kd3, depth + 1, p + p->offset, x, b); } } static void kd3_print(kd3_tree* kd3) { int a[3], b[3]; a[0] = a[1] = a[2] = INT_MIN; b[0] = b[1] = b[2] = INT_MAX; kd3_print_depth(kd3, 0, kd3->tree, a, b); } #endif void kd3_build_xradius(kd3_tree* kd3) { int i, j; /* create xradius */ if (kd3->xradius) return; kd3->xradius = Gif_NewArray(unsigned, kd3->nitems); for (i = 0; i != kd3->nitems; ++i) kd3->xradius[i] = (unsigned) -1; for (i = 0; i != kd3->nitems; ++i) for (j = i + 1; j != kd3->nitems; ++j) { unsigned dist = kc_distance(kd3->ks[i], kd3->ks[j]); unsigned radius = dist / 4; if (radius < kd3->xradius[i]) kd3->xradius[i] = radius; if (radius < kd3->xradius[j]) kd3->xradius[j] = radius; } } void kd3_build(kd3_tree* kd3) { int i, delta, *perm; assert(!kd3->tree); /* create tree */ kd3->tree = Gif_NewArray(kd3_treepos, 256); kd3->ntree = 256; kd3->maxdepth = 0; /* create copy of items; remove duplicates */ perm = Gif_NewArray(int, kd3->nitems); for (i = 0; i != kd3->nitems; ++i) perm[i] = i; #if ENABLE_THREADS /* * Because kd3_sorter is a static global used in some * sorting comparators, put a mutex around this * code block to avoid an utter catastrophe. */ pthread_mutex_lock(&kd3_sort_lock); #endif kd3_sorter = kd3; qsort(perm, kd3->nitems, sizeof(int), kd3_item_compare_all); for (i = 0, delta = 1; i + delta < kd3->nitems; ++i) if (memcmp(&kd3->ks[perm[i]], &kd3->ks[perm[i+delta]], sizeof(kcolor)) == 0) ++delta, --i; else if (delta > 1) perm[i+1] = perm[i+delta]; kd3_build_range(perm, kd3->nitems - (delta - 1), 0, 0); assert(kd3->maxdepth < 32); #if ENABLE_THREADS pthread_mutex_unlock(&kd3_sort_lock); #endif Gif_DeleteArray(perm); } void kd3_init_build(kd3_tree* kd3, kcolor (*transform)(int, int, int), const Gif_Colormap* gfcm) { int i; kd3_init(kd3, transform); for (i = 0; i < gfcm->ncol; ++i) kd3_add8g(kd3, gfcm->col[i].gfc_red, gfcm->col[i].gfc_green, gfcm->col[i].gfc_blue); kd3_build(kd3); } int kd3_closest_transformed(kd3_tree* kd3, kcolor k, unsigned* dist_store) { const kd3_treepos* stack[32]; uint8_t state[32]; int stackpos = 0; int result = -1; unsigned mindist = (unsigned) -1; if (!kd3->tree) kd3_build(kd3); stack[0] = kd3->tree; state[0] = 0; while (stackpos >= 0) { const kd3_treepos* p; assert(stackpos < 32); p = stack[stackpos]; if (p->offset < 0) { if (p->pivot >= 0 && kd3->disabled != p->pivot) { unsigned dist = kc_distance(kd3->ks[p->pivot], k); if (dist < mindist) { mindist = dist; result = p->pivot; } } if (--stackpos >= 0) ++state[stackpos]; } else if (state[stackpos] == 0) { if (k.a[stackpos % 3] < p->pivot) stack[stackpos + 1] = p + 1; else stack[stackpos + 1] = p + p->offset; ++stackpos; state[stackpos] = 0; } else { int delta = k.a[stackpos % 3] - p->pivot; if (state[stackpos] == 1 && (unsigned) delta * (unsigned) delta < mindist) { if (delta < 0) stack[stackpos + 1] = p + p->offset; else stack[stackpos + 1] = p + 1; ++stackpos; state[stackpos] = 0; } else if (--stackpos >= 0) ++state[stackpos]; } } if (dist_store) *dist_store = mindist; return result; } gifsicle-1.96/src/kcolor.h000066400000000000000000000265761475770763400155330ustar00rootroot00000000000000/* kcolor.h - Color-oriented function declarations for gifsicle. Copyright (C) 2013-2021 Eddie Kohler, ekohler@gmail.com This file is part of gifsicle. Gifsicle is free software. It is distributed under the GNU Public License, version 2; you can copy, distribute, or alter it at will, as long as this notice is kept intact and this source code is made available. There is no warranty, express or implied. */ #ifndef GIFSICLE_KCOLOR_H #define GIFSICLE_KCOLOR_H #include #include /* kcolor: a 3D vector, each component has 15 bits of precision */ /* 15 bits means KC_MAX * KC_MAX always fits within a signed 32-bit integer, and a 3-D squared distance always fits within an unsigned 32-bit integer. */ #define KC_MAX 0x7FFF #define KC_WHOLE 0x8000 #define KC_HALF 0x4000 #define KC_QUARTER 0x2000 #define KC_BITS 15 typedef struct kcolor { int16_t a[3]; } kcolor; #undef min #undef max #define min(a, b) ((a) < (b) ? (a) : (b)) #define max(a, b) ((a) > (b) ? (a) : (b)) #define KC_CLAMPV(v) (max(0, min((v), KC_MAX))) typedef union kacolor { kcolor k; int16_t a[4]; #if HAVE_INT64_T int64_t q; /* to get better alignment */ #endif } kacolor; /* gamma_tables[0]: array of 256 gamma-conversion values gamma_tables[1]: array of 256 reverse gamma-conversion values */ extern const uint16_t* gamma_tables[2]; /* return the gamma transformation of `a0/a1/a2` [RGB] */ static inline kcolor kc_make8g(int a0, int a1, int a2) { kcolor kc; kc.a[0] = gamma_tables[0][a0]; kc.a[1] = gamma_tables[0][a1]; kc.a[2] = gamma_tables[0][a2]; return kc; } /* return the gamma transformation of `*gfc` */ static inline kcolor kc_makegfcg(const Gif_Color* gfc) { return kc_make8g(gfc->gfc_red, gfc->gfc_green, gfc->gfc_blue); } /* return the uncorrected representation of `a0/a1/a2` [RGB] */ static inline kcolor kc_make8ng(int a0, int a1, int a2) { kcolor kc; kc.a[0] = (a0 << 7) + (a0 >> 1); kc.a[1] = (a1 << 7) + (a1 >> 1); kc.a[2] = (a2 << 7) + (a2 >> 1); return kc; } /* return the kcolor representation of `*gfc` (no gamma transformation) */ static inline kcolor kc_makegfcng(const Gif_Color* gfc) { return kc_make8ng(gfc->gfc_red, gfc->gfc_green, gfc->gfc_blue); } /* return transparency */ static inline kacolor kac_transparent() { kacolor x; x.a[0] = x.a[1] = x.a[2] = x.a[3] = 0; return x; } /* return a hex color string definition for `x` */ const char* kc_debug_str(kcolor x); /* set `*x` to the reverse gamma transformation of `*x` */ kcolor kc_revgamma_transform(kcolor x); /* return the reverse gramma transformation of `*x` as a Gif_Color */ static inline Gif_Color kc_togfcg(kcolor x) { Gif_Color gfc; x = kc_revgamma_transform(x); gfc.gfc_red = (uint8_t) (x.a[0] >> 7); gfc.gfc_green = (uint8_t) (x.a[1] >> 7); gfc.gfc_blue = (uint8_t) (x.a[2] >> 7); gfc.haspixel = 0; return gfc; } /* return the squared Euclidean distance between `*x` and `*y` */ static inline uint32_t kc_distance(kcolor x, kcolor y) { /* It’s OK to use unsigned multiplication for this: the low 32 bits are the same either way. Unsigned avoids undefined behavior. */ uint32_t d0 = (uint32_t) x.a[0] - (uint32_t) y.a[0]; uint32_t d1 = (uint32_t) x.a[1] - (uint32_t) y.a[1]; uint32_t d2 = (uint32_t) x.a[2] - (uint32_t) y.a[2]; return d0 * d0 + d1 * d1 + d2 * d2; } /* return the luminance value for `*x`; result is between 0 and KC_MAX */ static inline int kc_luminance(kcolor kc) { return (55 * kc.a[0] + 183 * kc.a[1] + 19 * kc.a[2]) >> 8; } /* set `*x` to the grayscale version of `*x`, transformed by luminance */ static inline kcolor kc_luminance_transform(int a0, int a1, int a2) { /* For grayscale colormaps, use distance in luminance space instead of distance in RGB space. The weights for the R,G,B components in luminance space are 0.2126,0.7152,0.0722. (That's ITU primaries, which are compatible with sRGB; NTSC recommended our previous values, 0.299,0.587,0.114.) Using the proportional factors 55,183,19 we get a scaled gray value between 0 and 255 * 257; dividing by 256 gives us what we want. Thanks to Christian Kumpf, , for providing a patch. */ kcolor kc = kc_make8g(a0, a1, a2); kc.a[0] = kc.a[1] = kc.a[2] = kc_luminance(kc); return kc; } kcolor kc_oklab_transform(int a0, int a1, int a2); /* wkcolor: like kcolor, but components are 32 bits instead of 16 */ typedef struct wkcolor { int32_t a[3]; } wkcolor; static inline wkcolor wkc_zero(void) { wkcolor wkc; wkc.a[0] = wkc.a[1] = wkc.a[2] = 0; return wkc; } static inline int wkc_iszero(wkcolor wkc) { return wkc.a[0] == 0 && wkc.a[1] == 0 && wkc.a[2] == 0; } static inline kcolor kc_adjust(kcolor k, wkcolor delta) { k.a[0] = KC_CLAMPV((int32_t) k.a[0] + delta.a[0]); k.a[1] = KC_CLAMPV((int32_t) k.a[1] + delta.a[1]); k.a[2] = KC_CLAMPV((int32_t) k.a[2] + delta.a[2]); return k; } /* kd3_tree: kd-tree for 3 dimensions, indexing kcolors */ typedef struct kd3_tree kd3_tree; typedef struct kd3_treepos kd3_treepos; struct kd3_tree { kd3_treepos* tree; int ntree; int disabled; kcolor* ks; int nitems; int items_cap; int maxdepth; kcolor (*transform)(int, int, int); unsigned* xradius; }; /* initialize `kd3` with the given color `transform` (may be NULL) */ void kd3_init(kd3_tree* kd3, kcolor (*transform)(int, int, int)); /* free `kd3` */ void kd3_cleanup(kd3_tree* kd3); /* return the transformed color for 8-bit color `a0/a1/a2` (RGB) */ static inline kcolor kd3_make8g(kd3_tree* kd3, int a0, int a1, int a2) { return kd3->transform(a0, a1, a2); } /* return the transformed color for `*gfc` */ static inline kcolor kd3_makegfcg(kd3_tree* kd3, const Gif_Color* gfc) { return kd3_make8g(kd3, gfc->gfc_red, gfc->gfc_green, gfc->gfc_blue); } /* add the transformed color `k` to `*kd3` (do not apply `kd3->transform`). */ void kd3_add_transformed(kd3_tree* kd3, kcolor k); /* given 8-bit color `a0/a1/a2` (RGB), transform it by `kd3->transform` (e.g., apply gamma), and add it to `*kd3` */ static inline void kd3_add8g(kd3_tree* kd3, int a0, int a1, int a2) { kd3_add_transformed(kd3, kd3_make8g(kd3, a0, a1, a2)); } /* set `kd3->xradius`. given color `i`, `kd3->xradius[i]` is the square of the color's uniquely owned neighborhood. If `kc_distance(&kd3->ks[i], &k) < kd3->xradius[i]`, then `kd3_closest_transformed(kd3, &k) == i`. */ void kd3_build_xradius(kd3_tree* kd3); /* build the actual kd-tree for `kd3`. must be called before kd3_closest. */ void kd3_build(kd3_tree* kd3); /* kd3_init + kd3_add8g for all colors in `gfcm` + kd3_build */ void kd3_init_build(kd3_tree* kd3, kcolor (*transform)(int, int, int), const Gif_Colormap* gfcm); /* return the index of the color in `*kd3` closest to `k`. if `dist!=NULL`, store the distance from `k` to that index in `*dist`. */ int kd3_closest_transformed(kd3_tree* kd3, kcolor k, unsigned* dist); /* given 8-bit color `a0/a1/a2` (RGB), transform it by `kd3->transform` (e.g., apply gamma), and return the index of the color in `*kd3` closest to the result. */ static inline int kd3_closest8g(kd3_tree* kd3, int a0, int a1, int a2) { return kd3_closest_transformed(kd3, kd3_make8g(kd3, a0, a1, a2), NULL); } /* disable color index `i` in `*kd3`: it will never be returned by `kd3_closest*` */ static inline void kd3_disable(kd3_tree* kd3, int i) { assert((unsigned) i < (unsigned) kd3->nitems); assert(kd3->disabled < 0 || kd3->disabled == i); kd3->disabled = i; } /* enable all color indexes in `*kd3` */ static inline void kd3_enable_all(kd3_tree* kd3) { kd3->disabled = -1; } typedef uint32_t kchist_count_t; typedef struct kchistitem { kacolor ka; kchist_count_t count; } kchistitem; typedef struct kchist { kchistitem* h; int n; int capacity; } kchist; void kchist_init(kchist* kch); void kchist_cleanup(kchist* kch); void kchist_make(kchist* kch, Gif_Stream* gfs, uint32_t* ntransp); kchistitem* kchist_add(kchist* kch, kcolor color, kchist_count_t count); void kchist_compress(kchist* kch); static inline int kchistitem_compare_red(const void* va, const void* vb) { const kchistitem* a = (const kchistitem*) va; const kchistitem* b = (const kchistitem*) vb; return a->ka.a[0] - b->ka.a[0]; } static inline int kchistitem_compare_green(const void* va, const void* vb) { const kchistitem* a = (const kchistitem*) va; const kchistitem* b = (const kchistitem*) vb; return a->ka.a[1] - b->ka.a[1]; } static inline int kchistitem_compare_blue(const void* va, const void* vb) { const kchistitem* a = (const kchistitem*) va; const kchistitem* b = (const kchistitem*) vb; return a->ka.a[2] - b->ka.a[2]; } static inline int kchistitem_compare_popularity(const void* va, const void* vb) { const kchistitem* a = (const kchistitem*) va; const kchistitem* b = (const kchistitem*) vb; return a->count > b->count ? -1 : a->count != b->count; } typedef struct { kchist* kch; int* closest; uint32_t* min_dist; uint32_t* min_dither_dist; int* chosen; int nchosen; } kcdiversity; void kcdiversity_init(kcdiversity* div, kchist* kch, int dodither); void kcdiversity_cleanup(kcdiversity* div); int kcdiversity_find_popular(kcdiversity* div); int kcdiversity_find_diverse(kcdiversity* div, double ditherweight); int kcdiversity_choose(kcdiversity* div, int chosen, int dodither); #if HAVE_SIMD && HAVE_VECTOR_SIZE_VECTOR_TYPES typedef float float4 __attribute__((vector_size (sizeof(float) * 4))); typedef int int4 __attribute__((vector_size (sizeof(int) * 4))); #elif HAVE_SIMD && HAVE_EXT_VECTOR_TYPE_VECTOR_TYPES typedef float float4 __attribute__((ext_vector_type (4))); #else typedef float float4[4]; #endif typedef union scale_color { float4 a; } scale_color; static inline void sc_clear(scale_color* x) { x->a[0] = x->a[1] = x->a[2] = x->a[3] = 0; } static inline scale_color sc_makekc(kcolor k) { scale_color sc; sc.a[0] = k.a[0]; sc.a[1] = k.a[1]; sc.a[2] = k.a[2]; sc.a[3] = KC_MAX; return sc; } static inline scale_color sc_make(float a0, float a1, float a2, float a3) { scale_color sc; sc.a[0] = a0; sc.a[1] = a1; sc.a[2] = a2; sc.a[3] = a3; return sc; } #if HAVE_SIMD # define SCVEC_ADDV(sc, sc2) (sc).a += (sc2).a # define SCVEC_MULV(sc, sc2) (sc).a *= (sc2).a # define SCVEC_MULF(sc, f) (sc).a *= (f) # define SCVEC_DIVF(sc, f) (sc).a /= (f) # define SCVEC_ADDVxF(sc, sc2, f) (sc).a += (sc2).a * (f) # if HAVE___BUILTIN_SHUFFLEVECTOR # define SCVEC_ROT3(out, sc) do { (out).a = __builtin_shufflevector((sc).a, (sc).a, 1, 2, 0, 3); } while (0) # else # define SCVEC_ROT3(out, sc) do { int4 shufmask__ = {1, 2, 0, 3}; (out).a = __builtin_shuffle((sc).a, shufmask__); } while (0) # endif #else # define SCVEC_FOREACH(t) do { int k__; for (k__ = 0; k__ != 4; ++k__) { t; } } while (0) # define SCVEC_ADDV(sc, sc2) SCVEC_FOREACH((sc).a[k__] += (sc2).a[k__]) # define SCVEC_MULV(sc, sc2) SCVEC_FOREACH((sc).a[k__] *= (sc2).a[k__]) # define SCVEC_MULF(sc, f) SCVEC_FOREACH((sc).a[k__] *= (f)) # define SCVEC_DIVF(sc, f) SCVEC_FOREACH((sc).a[k__] /= (f)) # define SCVEC_ADDVxF(sc, sc2, f) SCVEC_FOREACH((sc).a[k__] += (sc2).a[k__] * (f)) # define SCVEC_ROT3(out, sc) do { float __a0 = (sc).a[0]; (out).a[0] = (sc).a[1]; (out).a[1] = (sc).a[2]; (out).a[2] = __a0; (out).a[3] = (sc).a[3]; } while (0) #endif #endif gifsicle-1.96/src/merge.c000066400000000000000000000274261475770763400153270ustar00rootroot00000000000000/* merge.c - Functions which actually combine and manipulate GIF image data. Copyright (C) 1997-2021 Eddie Kohler, ekohler@gmail.com This file is part of gifsicle. Gifsicle is free software. It is distributed under the GNU Public License, version 2; you can copy, distribute, or alter it at will, as long as this notice is kept intact and this source code is made available. There is no warranty, express or implied. */ #include #include "gifsicle.h" #include #include #include #include #include #include /* First merging stage: Mark the used colors in all colormaps. */ void unmark_colors(Gif_Colormap *gfcm) { int i; if (gfcm) for (i = 0; i < gfcm->ncol; i++) gfcm->col[i].haspixel = 0; } void unmark_colors_2(Gif_Colormap *gfcm) { int i; for (i = 0; i < gfcm->ncol; i++) { gfcm->col[i].pixel = 256; gfcm->col[i].haspixel = 0; } } void mark_used_colors(Gif_Stream *gfs, Gif_Image *gfi, Gt_Crop *crop, int compress_immediately) { Gif_Colormap *gfcm = gfi->local ? gfi->local : gfs->global; Gif_Color *col; int i, j, l, t, r, b, nleft, ncol, transp = gfi->transparent; /* There might not be a colormap. */ if (!gfcm) return; col = gfcm->col; ncol = gfcm->ncol; /* Mark color used for transparency. */ if (transp >= 0 && transp < ncol) col[transp].haspixel |= 2; /* Only mark colors until we've seen all of them. The left variable keeps track of how many are left. */ for (i = nleft = 0; i < ncol; ++i) if (!(col[i].haspixel & 1) && i != transp) ++nleft; if (nleft == 0) return; if (gfi->img || Gif_UncompressImage(gfs, gfi) == 2) compress_immediately = 0; /* Loop over every pixel (until we've seen all colors) */ if (crop) { Gt_Crop c; combine_crop(&c, crop, gfi); l = c.x; t = c.y; r = l + c.w; b = t + c.h; } else { l = t = 0; r = gfi->width; b = gfi->height; } for (j = t; j != b; ++j) { uint8_t *data = gfi->img[j] + l; for (i = l; i != r; ++i, ++data) if (*data < ncol && !(col[*data].haspixel & 1) && *data != transp) { col[*data].haspixel |= 1; --nleft; if (nleft == 0) goto done; } } done: if (compress_immediately > 0) Gif_ReleaseUncompressedImage(gfi); } int find_color_index(Gif_Color *c, int nc, Gif_Color *color) { int index; for (index = 0; index < nc; index++) if (GIF_COLOREQ(&c[index], color)) return index; return -1; } int merge_colormap_if_possible(Gif_Colormap *dest, Gif_Colormap *src) { Gif_Color *srccol; Gif_Color *destcol = dest->col; int destncol = dest->ncol; int dest_user_flags = dest->user_flags; int i, x; int trivial_map = 1; if (!src) return 1; srccol = src->col; for (i = 0; i < src->ncol; i++) { if (srccol[i].haspixel & 1) { /* Store an image color cell's mapping to the global colormap in its 'pixel' slot. This is useful caching: oftentimes many input frames will share a colormap */ int mapto = (srccol[i].pixel < 256 ? (int)srccol[i].pixel : -1); if (mapto == -1) mapto = find_color_index(destcol, destncol, &srccol[i]); if (mapto == -1 && destncol < 256) { /* add the color */ mapto = destncol; destcol[mapto] = srccol[i]; destncol++; } if (mapto == -1) /* check for a pure-transparent color */ for (x = 0; x < destncol; x++) if (destcol[x].haspixel == 2) { mapto = x; destcol[mapto] = srccol[i]; break; } if (mapto == -1) /* give up and require a local colormap */ goto local_colormap_required; assert(mapto >= 0 && mapto < destncol); assert(GIF_COLOREQ(&destcol[mapto], &srccol[i])); srccol[i].pixel = mapto; destcol[mapto].haspixel = 1; if (mapto != i) trivial_map = 0; } else if (srccol[i].haspixel & 2) { /* a dedicated transparent color; if trivial_map & at end of colormap insert it with haspixel == 2. (strictly not necessary; we do it to try to keep the map trivial.) */ if (trivial_map && i == destncol) { destcol[destncol] = srccol[i]; destncol++; } } } /* success! save new number of colors */ dest->ncol = destncol; dest->user_flags = dest_user_flags; return 1; /* failure: a local colormap is required */ local_colormap_required: if (warn_local_colormaps == 1) { static int context = 0; if (!context) { warning(1, "too many colors, using local colormaps\n" " (You may want to try %<--colors 256%>.)"); context = 1; } else warning(1, "too many colors, using local colormaps"); warn_local_colormaps = 2; } /* 9.Dec.1998 - This must have been a longstanding bug! We MUST clear the cached mappings of any pixels in the source colormap we assigned this time through, since we are throwing those colors away. We assigned it this time through if the cached mapping is >= dest->ncol. */ for (x = 0; x < i; x++) if ((srccol[x].haspixel & 1) && srccol[x].pixel >= (uint32_t)dest->ncol) srccol[x].pixel = 256; return 0; } void merge_stream(Gif_Stream *dest, Gif_Stream *src, int no_comments) { int i; assert(dest->global); /* unmark colors in global and local colormaps -- 12/9 */ if (src->global) unmark_colors_2(src->global); for (i = 0; i < src->nimages; i++) if (src->images[i]->local) unmark_colors_2(src->images[i]->local); if (dest->loopcount < 0) dest->loopcount = src->loopcount; if (src->end_comment && !no_comments) { if (!dest->end_comment) dest->end_comment = Gif_NewComment(); merge_comments(dest->end_comment, src->end_comment); } } void merge_comments(Gif_Comment *destc, Gif_Comment *srcc) { int i; for (i = 0; i < srcc->count; i++) Gif_AddComment(destc, srcc->str[i], srcc->len[i]); } static void merge_image_input_colors(uint8_t* inused, const Gif_Image* srci) { int i, x, y, nleft = Gif_ImageColorBound(srci); for (i = 0; i != 256; ++i) inused[i] = 0; for (y = 0; y != srci->height && nleft > 0; ++y) { const uint8_t* data = srci->img[y]; for (x = 0; x != srci->width; ++x, ++data) { nleft -= 1 - inused[*data]; inused[*data] = 1; } } if (srci->transparent >= 0) inused[srci->transparent] = 0; } Gif_Image * merge_image(Gif_Stream *dest, Gif_Stream *src, Gif_Image *srci, Gt_Frame* srcfr, int same_compressed_ok) { Gif_Colormap *imagecm; int imagecm_ncol; int i; Gif_Colormap *localcm = 0; Gif_Colormap *destcm = dest->global; uint8_t map[256]; /* map[input pixel value] == output pixval */ int trivial_map; /* does the map take input pixval --> the same pixel value for all colors in the image? */ uint8_t inused[256]; /* inused[input pival] == 1 iff used */ uint8_t used[256]; /* used[output pixval K] == 1 iff K was used in the image */ Gif_Image *desti; /* mark colors that were actually used in this image */ imagecm = srci->local ? srci->local : src->global; imagecm_ncol = imagecm ? imagecm->ncol : 0; merge_image_input_colors(inused, srci); for (i = imagecm_ncol; i != 256; ++i) if (inused[i]) { lwarning(srcfr->input_filename, "some colors undefined by colormap"); break; } /* map[old_pixel_value] == new_pixel_value */ for (i = 0; i < 256; i++) map[i] = used[i] = 0; /* Merge the colormap */ if (merge_colormap_if_possible(dest->global, imagecm)) { /* Create 'map' and 'used' for global colormap. */ for (i = 0; i != imagecm_ncol; ++i) if (inused[i]) map[i] = imagecm->col[i].pixel; } else { /* Need a local colormap. */ destcm = localcm = Gif_NewFullColormap(0, 256); for (i = 0; i != imagecm_ncol; ++i) if (inused[i]) { map[i] = localcm->ncol; localcm->col[localcm->ncol] = imagecm->col[i]; ++localcm->ncol; } } trivial_map = 1; for (i = 0; i != 256; ++i) if (inused[i]) { used[map[i]] = 1; trivial_map = trivial_map && map[i] == i; } /* Decide on a transparent index */ if (srci->transparent >= 0) { int found_transparent = -1; /* try to keep the map trivial -- prefer same transparent index */ if (trivial_map && !used[srci->transparent]) found_transparent = srci->transparent; else for (i = destcm->ncol - 1; i >= 0; i--) if (!used[i]) found_transparent = i; /* 1.Aug.1999 - Allow for the case that the transparent index is bigger than the number of colors we've created thus far. */ if (found_transparent < 0 || found_transparent >= destcm->ncol) { Gif_Color *c; found_transparent = destcm->ncol; /* 1.Aug.1999 - Don't update destcm->ncol -- we want the output colormap to be as small as possible. */ c = &destcm->col[found_transparent]; if (imagecm && srci->transparent < imagecm->ncol) *c = imagecm->col[srci->transparent]; c->haspixel = 2; assert(c->haspixel == 2 && found_transparent < 256); } map[srci->transparent] = found_transparent; if (srci->transparent != found_transparent) trivial_map = 0; } assert(destcm->ncol <= 256); /* Make the new image. */ desti = Gif_NewImage(); desti->identifier = Gif_CopyString(srci->identifier); if (srci->transparent > -1) desti->transparent = map[srci->transparent]; desti->delay = srci->delay; desti->disposal = srci->disposal; desti->left = srci->left; desti->top = srci->top; desti->interlace = srci->interlace; desti->width = srci->width; desti->height = srci->height; desti->local = localcm; if (trivial_map && same_compressed_ok && srci->compressed && !srci->compressed_errors) { desti->compressed_len = srci->compressed_len; desti->compressed = Gif_NewArray(uint8_t, srci->compressed_len); desti->free_compressed = Gif_Free; memcpy(desti->compressed, srci->compressed, srci->compressed_len); } else { int i, j; Gif_CreateUncompressedImage(desti, desti->interlace); if (trivial_map) for (j = 0; j < desti->height; j++) memcpy(desti->img[j], srci->img[j], desti->width); else for (j = 0; j < desti->height; j++) { uint8_t *srcdata = srci->img[j]; uint8_t *destdata = desti->img[j]; for (i = 0; i < desti->width; i++, srcdata++, destdata++) *destdata = map[*srcdata]; } } /* comments and extensions */ if (srci->comment) { desti->comment = Gif_NewComment(); merge_comments(desti->comment, srci->comment); } if (srci->extension_list && !srcfr->no_extensions) { Gif_Extension* gfex; for (gfex = srci->extension_list; gfex; gfex = gfex->next) if (gfex->kind != 255 || !srcfr->no_app_extensions) Gif_AddExtension(dest, desti, Gif_CopyExtension(gfex)); } while (srcfr->extensions) { Gif_Extension* next = srcfr->extensions->next; Gif_AddExtension(dest, desti, srcfr->extensions); srcfr->extensions = next; } Gif_AddImage(dest, desti); return desti; } gifsicle-1.96/src/optimize.c000066400000000000000000000313461475770763400160640ustar00rootroot00000000000000/* optimize.c - Functions to optimize animated GIFs. Copyright (C) 1997-2021 Eddie Kohler, ekohler@gmail.com This file is part of gifsicle. Gifsicle is free software. It is distributed under the GNU Public License, version 2; you can copy, distribute, or alter it at will, as long as this notice is kept intact and this source code is made available. There is no warranty, express or implied. */ #include #include "gifsicle.h" #include "kcolor.h" #include #include typedef int32_t penalty_type; typedef struct { int left; int top; int width; int height; } Gif_OptBounds; typedef struct { uint16_t left; uint16_t top; uint16_t width; uint16_t height; uint32_t size; uint8_t disposal; int transparent; uint8_t *needed_colors; unsigned required_color_count; int32_t active_penalty; int32_t global_penalty; int32_t colormap_penalty; Gif_Image *new_gfi; } Gif_OptData; /* Screen width and height */ static int screen_width; static int screen_height; /* Colormap containing all colors in the image. May have >256 colors */ static Gif_Colormap *all_colormap; /* Histogram so we can find colors quickly */ static kchist all_colormap_hist; /* The old global colormap, or a fake one we created if necessary */ static Gif_Colormap *in_global_map; /* The new global colormap */ static Gif_Colormap *out_global_map; #define TRANSP (0) #define NOT_IN_OUT_GLOBAL (256) static unsigned background; static int image_index; static penalty_type *permuting_sort_values; #define REQUIRED 2 #define REPLACE_TRANSP 1 /***** * SIMPLE HELPERS * new and delete optimize data; and colormap_combine; and sorting permutations **/ Gif_OptData * new_opt_data(void) { Gif_OptData *od = Gif_New(Gif_OptData); od->needed_colors = 0; od->global_penalty = 1; return od; } void delete_opt_data(Gif_OptData *od) { if (!od) return; Gif_DeleteArray(od->needed_colors); Gif_Delete(od); } /* all_colormap_add: Ensure that each color in 'src' is represented in 'all_colormap'. For each color 'i' in 'src', src->col[i].pixel == some j so that GIF_COLOREQ(&src->col[i], &all_colormap->col[j]). all_colormap->col[0] is reserved for transparency; no source color will be mapped to it. */ static void all_colormap_add(const Gif_Colormap* src) { int i; /* expand dst->col if necessary. This might change dst->col */ if (all_colormap->ncol + src->ncol >= all_colormap->capacity) { all_colormap->capacity *= 2; Gif_ReArray(all_colormap->col, Gif_Color, all_colormap->capacity); } for (i = 0; i < src->ncol; ++i) { kchistitem* khi = kchist_add(&all_colormap_hist, kc_makegfcng(&src->col[i]), 0); if (!khi->count) { all_colormap->col[all_colormap->ncol] = src->col[i]; all_colormap->col[all_colormap->ncol].pixel = 0; khi->count = all_colormap->ncol; ++all_colormap->ncol; } src->col[i].pixel = khi->count; } } /***** * MANIPULATING IMAGE AREAS **/ static Gif_OptBounds safe_bounds(Gif_Image *area) { /* Returns bounds constrained to lie within the screen. */ Gif_OptBounds b; b.left = constrain(0, area->left, screen_width); b.top = constrain(0, area->top, screen_height); b.width = constrain(0, area->left + area->width, screen_width) - b.left; b.height = constrain(0, area->top + area->height, screen_height) - b.top; return b; } /***** * FIND THE SMALLEST BOUNDING RECTANGLE ENCLOSING ALL CHANGES **/ /* fix_difference_bounds: make sure the image isn't 0x0. */ static void fix_difference_bounds(Gif_OptData *bounds) { if (bounds->width == 0 || bounds->height == 0) { bounds->top = 0; bounds->left = 0; bounds->width = 1; bounds->height = 1; } /* assert that image lies completely within screen */ assert(bounds->top < screen_height && bounds->left < screen_width && bounds->top + bounds->height <= screen_height && bounds->left + bounds->width <= screen_width); } /***** * CALCULATE OUTPUT GLOBAL COLORMAP **/ static void increment_penalties(Gif_OptData *opt, penalty_type *penalty, int32_t delta) { int i; int all_ncol = all_colormap->ncol; uint8_t *need = opt->needed_colors; for (i = 1; i < all_ncol; i++) if (need[i] == REQUIRED) penalty[i] += delta; } /***** * CREATE COLOR MAPPING FOR A PARTICULAR IMAGE **/ /* sort_colormap_permutation_rgb: for canonicalizing local colormaps by arranging them in RGB order */ static int colormap_rgb_permutation_sorter(const void *v1, const void *v2) { const Gif_Color *col1 = (const Gif_Color *)v1; const Gif_Color *col2 = (const Gif_Color *)v2; int value1 = (col1->gfc_red << 16) | (col1->gfc_green << 8) | col1->gfc_blue; int value2 = (col2->gfc_red << 16) | (col2->gfc_green << 8) | col2->gfc_blue; return value1 - value2; } /* prepare_colormap_map: Create and return an array of bytes mapping from global pixel values to pixel values for this image. It may add colormap cells to 'into'; if there isn't enough room in 'into', it will return 0. It sets the 'transparent' field of 'gfi->optdata', but otherwise doesn't change or read it at all. */ static uint8_t * prepare_colormap_map(Gif_Image *gfi, Gif_Colormap *into, uint8_t *need) { int i; int is_global = (into == out_global_map); int all_ncol = all_colormap->ncol; Gif_Color *all_col = all_colormap->col; int ncol = into->ncol; Gif_Color *col = into->col; uint8_t *map = Gif_NewArray(uint8_t, all_ncol); uint8_t into_used[256]; /* keep track of which pixel indices in 'into' have been used; initially, all unused */ for (i = 0; i < 256; i++) into_used[i] = 0; /* go over all non-transparent global pixels which MUST appear (need[P]==REQUIRED) and place them in 'into' */ for (i = 1; i < all_ncol; i++) { int val; if (need[i] != REQUIRED) continue; /* fail if a needed pixel isn't in the global map */ if (is_global) { val = all_col[i].pixel; if (val >= ncol) goto error; } else { /* always place colors in a local colormap */ if (ncol == 256) goto error; val = ncol; col[val] = all_col[i]; col[val].pixel = i; ncol++; } map[i] = val; into_used[val] = 1; } if (!is_global) { qsort(col, ncol, sizeof(Gif_Color), colormap_rgb_permutation_sorter); for (i = 0; i < ncol; ++i) map[col[i].pixel] = i; } /* now check for transparency */ gfi->transparent = -1; if (need[TRANSP]) { int transparent = -1; /* first, look for an unused index in 'into'. Pick the lowest one: the lower transparent index we get, the more likely we can shave a bit off min_code_bits later, thus saving space */ for (i = 0; i < ncol; i++) if (!into_used[i]) { transparent = i; break; } /* otherwise, [1.Aug.1999] use a fake slot for the purely transparent color. Don't actually enter the transparent color into the colormap -- we might be able to output a smaller colormap! If there's no room for it, give up */ if (transparent < 0) { if (ncol < 256) { transparent = ncol; /* 1.Aug.1999 - don't increase ncol */ col[ncol] = all_col[TRANSP]; } else goto error; } /* change mapping */ map[TRANSP] = transparent; for (i = 1; i < all_ncol; i++) if (need[i] == REPLACE_TRANSP) map[i] = transparent; gfi->transparent = transparent; } /* If we get here, it worked! Commit state changes (the number of color cells in 'into') and return the map. */ into->ncol = ncol; return map; error: /* If we get here, it failed! Return 0 and don't change global state. */ Gif_DeleteArray(map); return 0; } /* prepare_colormap: make a colormap up from the image data by fitting any used colors into a colormap. Returns a map from global color index to index in this image's colormap. May set a local colormap on 'gfi'. */ static uint8_t * prepare_colormap(Gif_Image *gfi, uint8_t *need) { uint8_t *map; /* try to map pixel values into the global colormap */ Gif_DeleteColormap(gfi->local); gfi->local = 0; map = prepare_colormap_map(gfi, out_global_map, need); if (!map) { /* that didn't work; add a local colormap. */ gfi->local = Gif_NewFullColormap(0, 256); map = prepare_colormap_map(gfi, gfi->local, need); } return map; } /***** * INITIALIZATION AND FINALIZATION **/ static int initialize_optimizer(Gif_Stream *gfs) { int i; if (gfs->nimages < 1) return 0; /* combine colormaps */ all_colormap = Gif_NewFullColormap(1, 384); all_colormap->col[0].gfc_red = 255; all_colormap->col[0].gfc_green = 255; all_colormap->col[0].gfc_blue = 255; in_global_map = gfs->global; if (!in_global_map) { Gif_Color *col; in_global_map = Gif_NewFullColormap(256, 256); col = in_global_map->col; for (i = 0; i < 256; i++, col++) col->gfc_red = col->gfc_green = col->gfc_blue = i; } { int any_globals = 0; int first_transparent = -1; kchist_init(&all_colormap_hist); for (i = 0; i < gfs->nimages; i++) { Gif_Image *gfi = gfs->images[i]; if (gfi->local) all_colormap_add(gfi->local); else any_globals = 1; if (gfi->transparent >= 0 && first_transparent < 0) first_transparent = i; } if (any_globals) all_colormap_add(in_global_map); kchist_cleanup(&all_colormap_hist); /* try and maintain transparency's pixel value */ if (first_transparent >= 0) { Gif_Image *gfi = gfs->images[first_transparent]; Gif_Colormap *gfcm = gfi->local ? gfi->local : gfs->global; all_colormap->col[TRANSP] = gfcm->col[gfi->transparent]; } } /* find screen_width and screen_height, and clip all images to screen */ Gif_CalculateScreenSize(gfs, 0); screen_width = gfs->screen_width; screen_height = gfs->screen_height; for (i = 0; i < gfs->nimages; i++) Gif_ClipImage(gfs->images[i], 0, 0, screen_width, screen_height); /* choose background */ if (gfs->images[0]->transparent < 0 && gfs->global && gfs->background < in_global_map->ncol) background = in_global_map->col[gfs->background].pixel; else background = TRANSP; return 1; } static void finalize_optimizer(Gif_Stream *gfs, int optimize_flags) { int i; if (background == TRANSP) gfs->background = (uint8_t)gfs->images[0]->transparent; /* 11.Mar.2010 - remove entirely transparent frames. */ for (i = 1; i < gfs->nimages && !(optimize_flags & GT_OPT_KEEPEMPTY); ++i) { Gif_Image *gfi = gfs->images[i]; if (gfi->width == 1 && gfi->height == 1 && gfi->transparent >= 0 && !gfi->identifier && !gfi->comment && (gfi->disposal == GIF_DISPOSAL_ASIS || gfi->disposal == GIF_DISPOSAL_NONE || gfi->disposal == GIF_DISPOSAL_PREVIOUS) && gfi->delay && gfs->images[i-1]->delay) { Gif_UncompressImage(gfs, gfi); if (gfi->img[0][0] == gfi->transparent && (gfs->images[i-1]->disposal == GIF_DISPOSAL_ASIS || gfs->images[i-1]->disposal == GIF_DISPOSAL_NONE)) { gfs->images[i-1]->delay += gfi->delay; Gif_DeleteImage(gfi); memmove(&gfs->images[i], &gfs->images[i+1], sizeof(Gif_Image *) * (gfs->nimages - i - 1)); --gfs->nimages; --i; } } } /* 10.Dec.1998 - prefer GIF_DISPOSAL_NONE to GIF_DISPOSAL_ASIS. This is semantically "wrong" -- it's better to set the disposal explicitly than rely on default behavior -- but will result in smaller GIF files, since the graphic control extension can be left off in many cases. */ for (i = 0; i < gfs->nimages; i++) if (gfs->images[i]->disposal == GIF_DISPOSAL_ASIS && gfs->images[i]->delay == 0 && gfs->images[i]->transparent < 0) gfs->images[i]->disposal = GIF_DISPOSAL_NONE; Gif_DeleteColormap(in_global_map); Gif_DeleteColormap(all_colormap); } /* two versions of the optimization template */ #define palindex_type uint16_t #define X(t) t ## 16 #include "opttemplate.c" #undef palindex_type #undef X #define palindex_type uint32_t #define X(t) t ## 32 #include "opttemplate.c" /* the interface function! */ void optimize_fragments(Gif_Stream *gfs, int optimize_flags, int huge_stream) { if (!initialize_optimizer(gfs)) return; if ((unsigned) all_colormap->ncol >= 0xFFFF) { create_subimages32(gfs, optimize_flags, !huge_stream); create_out_global_map32(gfs); create_new_image_data32(gfs, optimize_flags); finalize_optimizer_data32(); } else { create_subimages16(gfs, optimize_flags, !huge_stream); create_out_global_map16(gfs); create_new_image_data16(gfs, optimize_flags); finalize_optimizer_data16(); } finalize_optimizer(gfs, optimize_flags); } gifsicle-1.96/src/opttemplate.c000066400000000000000000001000771475770763400165600ustar00rootroot00000000000000/* opttemplate.c - Functions to optimize animated GIFs. Copyright (C) 1997-2021 Eddie Kohler, ekohler@gmail.com This file is part of gifsicle. Gifsicle is free software. It is distributed under the GNU Public License, version 2; you can copy, distribute, or alter it at will, as long as this notice is kept intact and this source code is made available. There is no warranty, express or implied. */ static palindex_type *X(last_data); static palindex_type *X(this_data); static palindex_type *X(next_data); /* sort_permutation: sorts a given permutation 'perm' according to the corresponding values in 'values'. Thus, in the output, the sequence '[ values[perm[i]] | i <- 0..size-1 ]' will be monotonic, either up or (if is_down != 0) down. */ /* 9.Dec.1998 - Dumb idiot, it's time you stopped using C. The optimizer was broken because I switched to uint32_t's for the sorting values without considering the consequences; and the consequences were bad. */ static int X(permuting_sorter_up)(const void *v1, const void *v2) { const palindex_type *n1 = (const palindex_type *)v1; const palindex_type *n2 = (const palindex_type *)v2; if (permuting_sort_values[*n1] < permuting_sort_values[*n2]) return -1; else if (permuting_sort_values[*n1] == permuting_sort_values[*n2]) return 0; else return 1; } static int X(permuting_sorter_down)(const void *v1, const void *v2) { const palindex_type *n1 = (const palindex_type *)v1; const palindex_type *n2 = (const palindex_type *)v2; if (permuting_sort_values[*n1] > permuting_sort_values[*n2]) return -1; else if (permuting_sort_values[*n1] == permuting_sort_values[*n2]) return 0; else return 1; } static palindex_type* X(sort_permutation)(palindex_type *perm, int size, penalty_type *values, int is_down) { permuting_sort_values = values; if (is_down) qsort(perm, size, sizeof(palindex_type), X(permuting_sorter_down)); else qsort(perm, size, sizeof(palindex_type), X(permuting_sorter_up)); permuting_sort_values = 0; return perm; } /***** * MANIPULATING IMAGE AREAS **/ static void X(copy_data_area)(palindex_type *dst, palindex_type *src, Gif_Image *area) { Gif_OptBounds ob; int y; if (!area) return; ob = safe_bounds(area); dst += ob.top * (unsigned) screen_width + ob.left; src += ob.top * (unsigned) screen_width + ob.left; for (y = 0; y < ob.height; y++) { memcpy(dst, src, sizeof(palindex_type) * ob.width); dst += screen_width; src += screen_width; } } static void X(copy_data_area_subimage)(palindex_type *dst, palindex_type *src, Gif_OptData *area) { Gif_Image img; img.left = area->left; img.top = area->top; img.width = area->width; img.height = area->height; X(copy_data_area)(dst, src, &img); } static void X(erase_data_area)(palindex_type *dst, Gif_Image *area) { int x, y; Gif_OptBounds ob = safe_bounds(area); dst += ob.top * (unsigned) screen_width + ob.left; for (y = 0; y < ob.height; y++) { for (x = 0; x < ob.width; x++) dst[x] = TRANSP; dst += screen_width; } } static void X(erase_data_area_subimage)(palindex_type *dst, Gif_OptData *area) { Gif_Image img; img.left = area->left; img.top = area->top; img.width = area->width; img.height = area->height; X(erase_data_area)(dst, &img); } static void X(erase_screen)(palindex_type *dst) { uint32_t i; uint32_t screen_size = (unsigned) screen_width * (unsigned) screen_height; for (i = 0; i < screen_size; i++) *dst++ = TRANSP; } /***** * APPLY A GIF FRAME OR DISPOSAL TO AN IMAGE DESTINATION **/ static void X(apply_frame)(palindex_type *dst, Gif_Stream* gfs, Gif_Image* gfi, int replace, int save_uncompressed) { int i, y, was_compressed = 0; palindex_type map[256]; Gif_Colormap *colormap = gfi->local ? gfi->local : in_global_map; Gif_OptBounds ob = safe_bounds(gfi); if (!gfi->img) { was_compressed = 1; Gif_UncompressImage(gfs, gfi); } /* make sure transparency maps to TRANSP */ for (i = 0; i < colormap->ncol; i++) map[i] = colormap->col[i].pixel; /* out-of-bounds colors map to 0, for the sake of argument */ y = colormap->ncol ? colormap->col[0].pixel : 0; for (i = colormap->ncol; i < 256; i++) map[i] = y; if (gfi->transparent >= 0 && gfi->transparent < 256) map[gfi->transparent] = TRANSP; else replace = 1; /* map the image */ dst += ob.left + ob.top * (unsigned) screen_width; for (y = 0; y < ob.height; y++) { uint8_t *gfi_pointer = gfi->img[y]; int x; if (replace) for (x = 0; x < ob.width; x++) dst[x] = map[gfi_pointer[x]]; else for (x = 0; x < ob.width; x++) { palindex_type new_pixel = map[gfi_pointer[x]]; if (new_pixel != TRANSP) dst[x] = new_pixel; } dst += screen_width; } if (was_compressed && !save_uncompressed) Gif_ReleaseUncompressedImage(gfi); } static void X(apply_frame_disposal)(palindex_type *into_data, palindex_type *from_data, palindex_type *previous_data, Gif_Image *gfi) { unsigned screen_size = (unsigned) screen_width * (unsigned) screen_height; if (gfi->disposal == GIF_DISPOSAL_PREVIOUS) memcpy(into_data, previous_data, sizeof(palindex_type) * screen_size); else { memcpy(into_data, from_data, sizeof(palindex_type) * screen_size); if (gfi->disposal == GIF_DISPOSAL_BACKGROUND) X(erase_data_area)(into_data, gfi); } } /***** * FIND THE SMALLEST BOUNDING RECTANGLE ENCLOSING ALL CHANGES **/ /* find_difference_bounds: Find the smallest rectangular area containing all the changes and store it in 'bounds'. */ static void X(find_difference_bounds)(Gif_OptData *bounds, Gif_Image *gfi, Gif_Image *last) { int lf, rt, lf_min, rt_max, tp, bt, x, y; /* 1.Aug.99 - use current bounds if possible, since this function is a speed bottleneck */ if (!last || last->disposal == GIF_DISPOSAL_NONE || last->disposal == GIF_DISPOSAL_ASIS) { Gif_OptBounds ob = safe_bounds(gfi); lf_min = ob.left; rt_max = ob.left + ob.width - 1; tp = ob.top; bt = ob.top + ob.height - 1; } else { lf_min = 0; rt_max = screen_width - 1; tp = 0; bt = screen_height - 1; } for (; tp < screen_height; tp++) if (memcmp(X(last_data) + (unsigned) screen_width * tp, X(this_data) + (unsigned) screen_width * tp, screen_width * sizeof(palindex_type)) != 0) break; for (; bt >= tp; bt--) if (memcmp(X(last_data) + (unsigned) screen_width * bt, X(this_data) + (unsigned) screen_width * bt, screen_width * sizeof(palindex_type)) != 0) break; lf = screen_width; rt = 0; for (y = tp; y <= bt; y++) { palindex_type *ld = X(last_data) + (unsigned) screen_width * y; palindex_type *td = X(this_data) + (unsigned) screen_width * y; for (x = lf_min; x < lf; x++) if (ld[x] != td[x]) break; lf = x; for (x = rt_max; x > rt; x--) if (ld[x] != td[x]) break; rt = x; } /* 19.Aug.1999 - handle case when there's no difference between frames */ if (tp > bt) { tp = bt = constrain(0, gfi->top, screen_height - 1); lf = rt = constrain(0, gfi->left, screen_width - 1); } bounds->left = lf; bounds->top = tp; bounds->width = rt + 1 - lf; bounds->height = bt + 1 - tp; } /* expand_difference_bounds: If the current image has background disposal and the background is transparent, we must expand the difference bounds to include any blanked (newly transparent) pixels that are still transparent in the next image. This function does that by comparing this_data and next_data. The new bounds are passed and stored in 'bounds'; the image's old bounds, which are also the maximum bounds, are passed in 'this_bounds'. */ static int X(expand_difference_bounds)(Gif_OptData *bounds, Gif_Image *this_bounds) { int x, y, expanded = 0; Gif_OptBounds ob = safe_bounds(this_bounds); if (bounds->width <= 0 || bounds->height <= 0) { bounds->left = bounds->top = 0; bounds->width = screen_width; bounds->height = screen_height; } /* 20.Nov.2013 - The image `bounds` might be larger than `this_bounds` because of a previous frame's background disposal. Don't accidentally shrink `this_bounds`. */ if (ob.left > bounds->left) { ob.width = (ob.left + ob.width) - bounds->left; ob.left = bounds->left; } if (ob.top > bounds->top) { ob.height = (ob.top + ob.height) - bounds->top; ob.top = bounds->top; } if (ob.left + ob.width < bounds->left + bounds->width) ob.width = bounds->left + bounds->width - ob.left; if (ob.top + ob.height < bounds->top + bounds->height) ob.height = bounds->top + bounds->height - ob.top; for (; ob.top < bounds->top; ++ob.top, --ob.height) { palindex_type *now = X(this_data) + (unsigned) screen_width * ob.top; palindex_type *next = X(next_data) + (unsigned) screen_width * ob.top; for (x = ob.left; x < ob.left + ob.width; ++x) if (now[x] != TRANSP && next[x] == TRANSP) { expanded = 1; goto found_top; } } found_top: for (; ob.top + ob.height > bounds->top + bounds->height; --ob.height) { palindex_type *now = X(this_data) + (unsigned) screen_width * (ob.top + ob.height - 1); palindex_type *next = X(next_data) + (unsigned) screen_width * (ob.top + ob.height - 1); for (x = ob.left; x < ob.left + ob.width; ++x) if (now[x] != TRANSP && next[x] == TRANSP) { expanded = 1; goto found_bottom; } } found_bottom: for (; ob.left < bounds->left; ++ob.left, --ob.width) { palindex_type *now = X(this_data) + ob.left; palindex_type *next = X(next_data) + ob.left; for (y = ob.top; y < ob.top + ob.height; ++y) if (now[y * (unsigned) screen_width] != TRANSP && next[y * (unsigned) screen_width] == TRANSP) { expanded = 1; goto found_left; } } found_left: for (; ob.left + ob.width > bounds->left + bounds->width; --ob.width) { palindex_type *now = X(this_data) + ob.left + ob.width - 1; palindex_type *next = X(next_data) + ob.left + ob.width - 1; for (y = ob.top; y < ob.top + ob.height; ++y) if (now[y * (unsigned) screen_width] != TRANSP && next[y * (unsigned) screen_width] == TRANSP) { expanded = 1; goto found_right; } } found_right: if (!expanded) for (y = ob.top; y < ob.top + ob.height; ++y) { palindex_type *now = X(this_data) + y * (unsigned) screen_width; palindex_type *next = X(next_data) + y * (unsigned) screen_width; for (x = ob.left; x < ob.left + ob.width; ++x) if (now[x] != TRANSP && next[x] == TRANSP) { expanded = 1; break; } } bounds->left = ob.left; bounds->top = ob.top; bounds->width = ob.width; bounds->height = ob.height; return expanded; } /***** * DETERMINE WHICH COLORS ARE USED **/ /* get_used_colors: mark which colors are needed by a given image. Returns a need array so that need[j] == REQUIRED if the output colormap must include all_color j; REPLACE_TRANSP if it should be replaced by transparency; and 0 if it's not in the image at all. If use_transparency > 0, then a pixel which was the same in the last frame may be replaced with transparency. If use_transparency == 2, transparency MUST be set. (This happens on the first image if the background should be transparent.) */ static void X(get_used_colors)(Gif_OptData *bounds, int use_transparency) { int top = bounds->top, width = bounds->width, height = bounds->height; int i, x, y; int all_ncol = all_colormap->ncol; uint8_t *need = Gif_NewArray(uint8_t, all_ncol); for (i = 0; i < all_ncol; i++) need[i] = 0; /* set elements that are in the image. need == 2 means the color must be in the map; need == 1 means the color may be replaced by transparency. */ for (y = top; y < top + height; y++) { palindex_type *data = X(this_data) + (unsigned) screen_width * y + bounds->left; palindex_type *last = X(last_data) + (unsigned) screen_width * y + bounds->left; for (x = 0; x < width; x++) { if (data[x] != last[x]) need[data[x]] = REQUIRED; else if (need[data[x]] == 0) need[data[x]] = REPLACE_TRANSP; } } if (need[TRANSP]) need[TRANSP] = REQUIRED; /* check for too many colors; also force transparency if needed */ { int count[3]; /* Count distinct pixels in each category */ count[0] = count[1] = count[2] = 0; for (i = 0; i < all_ncol; i++) count[need[i]]++; /* If use_transparency is large and there's room, add transparency */ if (use_transparency > 1 && !need[TRANSP] && count[REQUIRED] < 256) { need[TRANSP] = REQUIRED; count[REQUIRED]++; } /* If too many "potentially transparent" pixels, force transparency */ if (count[REPLACE_TRANSP] + count[REQUIRED] > 256) use_transparency = 1; /* Make sure transparency is marked necessary if we use it */ if (count[REPLACE_TRANSP] > 0 && use_transparency && !need[TRANSP]) { need[TRANSP] = REQUIRED; count[REQUIRED]++; } /* If not using transparency, change "potentially transparent" pixels to "actually used" pixels */ if (!use_transparency) { for (i = 0; i < all_ncol; i++) if (need[i] == REPLACE_TRANSP) need[i] = REQUIRED; count[REQUIRED] += count[REPLACE_TRANSP]; } /* If we can afford to have transparency, and we want to use it, then include it */ if (count[REQUIRED] < 256 && use_transparency && !need[TRANSP]) { need[TRANSP] = REQUIRED; count[REQUIRED]++; } bounds->required_color_count = count[REQUIRED]; } bounds->needed_colors = need; } /***** * FIND SUBIMAGES AND COLORS USED **/ static void X(create_subimages)(Gif_Stream *gfs, int optimize_flags, int save_uncompressed) { unsigned screen_size; Gif_Image *last_gfi; int next_data_valid; palindex_type *previous_data = 0; int local_color_tables = 0; screen_size = (unsigned) screen_width * (unsigned) screen_height; X(last_data) = Gif_NewArray(palindex_type, screen_size); X(this_data) = Gif_NewArray(palindex_type, screen_size); X(next_data) = Gif_NewArray(palindex_type, screen_size); next_data_valid = 0; /* do first image. Remember to uncompress it if necessary */ X(erase_screen)(X(last_data)); X(erase_screen)(X(this_data)); last_gfi = 0; /* PRECONDITION: previous_data -- garbage last_data -- optimized image after disposal of previous optimized frame this_data -- input image after disposal of previous input frame next_data -- input image after application of current input frame, if next_image_valid */ for (image_index = 0; image_index < gfs->nimages; image_index++) { Gif_Image *gfi = gfs->images[image_index]; Gif_OptData *subimage = new_opt_data(); if (gfi->local) local_color_tables = 1; /* save previous data if necessary */ if (gfi->disposal == GIF_DISPOSAL_PREVIOUS || (local_color_tables && image_index > 0 && last_gfi->disposal > GIF_DISPOSAL_ASIS)) { if (!previous_data) previous_data = Gif_NewArray(palindex_type, screen_size); memcpy(previous_data, X(this_data), sizeof(palindex_type) * screen_size); } /* set this_data equal to the current image */ if (next_data_valid) { palindex_type *temp = X(this_data); X(this_data) = X(next_data); X(next_data) = temp; next_data_valid = 0; } else X(apply_frame)(X(this_data), gfs, gfi, 0, save_uncompressed); retry_frame: /* find minimum area of difference between this image and last image */ subimage->disposal = GIF_DISPOSAL_ASIS; if (image_index > 0) { X(find_difference_bounds)(subimage, gfi, last_gfi); } else { Gif_OptBounds ob = safe_bounds(gfi); subimage->left = ob.left; subimage->top = ob.top; subimage->width = ob.width; subimage->height = ob.height; } /* might need to expand difference border on background disposal */ if ((gfi->disposal == GIF_DISPOSAL_BACKGROUND || gfi->disposal == GIF_DISPOSAL_PREVIOUS) && image_index < gfs->nimages - 1) { /* set up next_data */ Gif_Image *next_gfi = gfs->images[image_index + 1]; X(apply_frame_disposal)(X(next_data), X(this_data), previous_data, gfi); X(apply_frame)(X(next_data), gfs, next_gfi, 0, save_uncompressed); next_data_valid = 1; /* expand border as necessary */ if (X(expand_difference_bounds)(subimage, gfi)) subimage->disposal = GIF_DISPOSAL_BACKGROUND; } fix_difference_bounds(subimage); /* set map of used colors */ { int use_transparency = (optimize_flags & GT_OPT_MASK) > 1 && image_index > 0; if (image_index == 0 && background == TRANSP) use_transparency = 2; X(get_used_colors)(subimage, use_transparency); /* Gifsicle's optimization strategy normally creates frames with ASIS or BACKGROUND disposal (not PREVIOUS disposal). However, there are cases when PREVIOUS disposal is strictly required, or a frame would require more than 256 colors. Detect this case and try to recover. */ if (subimage->required_color_count > 256) { if (image_index > 0 && local_color_tables) { Gif_OptData *subimage = (Gif_OptData*) last_gfi->user_data; if ((last_gfi->disposal == GIF_DISPOSAL_PREVIOUS || last_gfi->disposal == GIF_DISPOSAL_BACKGROUND) && subimage->disposal != last_gfi->disposal) { subimage->disposal = last_gfi->disposal; memcpy(X(last_data), previous_data, sizeof(palindex_type) * screen_size); goto retry_frame; } } fatal_error("%d colors required in a frame (256 is max)", subimage->required_color_count); } } gfi->user_data = subimage; last_gfi = gfi; /* Apply optimized disposal to last_data and unoptimized disposal to this_data. Before 9.Dec.1998 I applied unoptimized disposal uniformly to both. This led to subtle bugs. After all, to determine bounds, we want to compare the current image (only obtainable through unoptimized disposal) with what WILL be left after the previous OPTIMIZED image's disposal. This fix is repeated in create_new_image_data */ if (subimage->disposal == GIF_DISPOSAL_BACKGROUND) X(erase_data_area_subimage)(X(last_data), subimage); else X(copy_data_area_subimage)(X(last_data), X(this_data), subimage); if (last_gfi->disposal == GIF_DISPOSAL_BACKGROUND) X(erase_data_area)(X(this_data), last_gfi); else if (last_gfi->disposal == GIF_DISPOSAL_PREVIOUS) { palindex_type *temp = previous_data; previous_data = X(this_data); X(this_data) = temp; } } Gif_DeleteArray(X(next_data)); if (previous_data) Gif_DeleteArray(previous_data); } /***** * CALCULATE OUTPUT GLOBAL COLORMAP **/ /* create_out_global_map: The interface function to this pass. It creates out_global_map and sets pixel values on all_colormap appropriately. Specifically: all_colormap->col[P].pixel >= 256 ==> P is not in the global colormap. Otherwise, all_colormap->col[P].pixel == the J so that GIF_COLOREQ(&all_colormap->col[P], &out_global_map->col[J]). On return, the 'colormap_penalty' component of an image's Gif_OptData structure is <0 iff that image will need a local colormap. 20.Aug.1999 - updated to new version that arranges the entire colormap, not just the stuff above 256 colors. */ static void X(create_out_global_map)(Gif_Stream *gfs) { int all_ncol = all_colormap->ncol; penalty_type *penalty = Gif_NewArray(penalty_type, all_ncol); palindex_type *permute = Gif_NewArray(palindex_type, all_ncol); palindex_type *ordering = Gif_NewArray(palindex_type, all_ncol); int cur_ncol, i, imagei; int nglobal_all = (all_ncol <= 257 ? all_ncol - 1 : 256); int permutation_changed; assert(all_ncol <= 0x7FFFFFFF); /* initial permutation is null */ for (i = 0; i < all_ncol - 1; i++) permute[i] = i + 1; /* choose appropriate penalties for each image */ for (imagei = 0; imagei < gfs->nimages; imagei++) { Gif_OptData *opt = (Gif_OptData *)gfs->images[imagei]->user_data; palindex_type pi; opt->global_penalty = opt->colormap_penalty = 1; for (pi = 2; pi < opt->required_color_count; pi *= 2) opt->colormap_penalty *= 3; opt->active_penalty = (all_ncol > 257 ? opt->colormap_penalty : opt->global_penalty); } /* set initial penalties for each color */ for (i = 1; i < all_ncol; i++) penalty[i] = 0; for (imagei = 0; imagei < gfs->nimages; imagei++) { Gif_OptData *opt = (Gif_OptData *)gfs->images[imagei]->user_data; increment_penalties(opt, penalty, opt->active_penalty); } permutation_changed = 1; /* Loop, removing one color at a time. */ for (cur_ncol = all_ncol - 1; cur_ncol; cur_ncol--) { palindex_type removed; /* sort permutation based on penalty */ if (permutation_changed) X(sort_permutation)(permute, cur_ncol, penalty, 1); permutation_changed = 0; /* update reverse permutation */ removed = permute[cur_ncol - 1]; ordering[removed] = cur_ncol - 1; /* decrement penalties for colors that are out of the running */ for (imagei = 0; imagei < gfs->nimages; imagei++) { Gif_OptData *opt = (Gif_OptData *)gfs->images[imagei]->user_data; uint8_t *need = opt->needed_colors; if (opt->global_penalty > 0 && need[removed] == REQUIRED) { increment_penalties(opt, penalty, -opt->active_penalty); opt->global_penalty = 0; opt->colormap_penalty = (cur_ncol > 256 ? -1 : 0); permutation_changed = 1; } } /* change colormap penalties if we're no longer working w/globalmap */ if (cur_ncol == 257) { for (i = 0; i < all_ncol; i++) penalty[i] = 0; for (imagei = 0; imagei < gfs->nimages; imagei++) { Gif_OptData *opt = (Gif_OptData *)gfs->images[imagei]->user_data; opt->active_penalty = opt->global_penalty; increment_penalties(opt, penalty, opt->global_penalty); } permutation_changed = 1; } } /* make sure background is in the global colormap */ if (background != TRANSP && ordering[background] >= 256) { palindex_type other = permute[255]; ordering[other] = ordering[background]; ordering[background] = 255; } /* assign out_global_map based on permutation */ out_global_map = Gif_NewFullColormap(nglobal_all, 256); for (i = 1; i < all_ncol; i++) if (ordering[i] < 256) { out_global_map->col[ordering[i]] = all_colormap->col[i]; all_colormap->col[i].pixel = ordering[i]; } else all_colormap->col[i].pixel = NOT_IN_OUT_GLOBAL; /* set the stream's background color */ if (background != TRANSP) gfs->background = ordering[background]; /* cleanup */ Gif_DeleteArray(penalty); Gif_DeleteArray(permute); Gif_DeleteArray(ordering); } /***** * CREATE OUTPUT FRAME DATA **/ /* simple_frame_data: just copy the data from the image into the frame data. No funkiness, no transparency, nothing */ static void X(simple_frame_data)(Gif_Image *gfi, uint8_t *map) { Gif_OptBounds ob = safe_bounds(gfi); int x, y; unsigned scan_width = gfi->width; for (y = 0; y < ob.height; y++) { palindex_type *from = X(this_data) + (unsigned) screen_width * (y + ob.top) + ob.left; uint8_t *into = gfi->image_data + y * scan_width; for (x = 0; x < ob.width; x++) *into++ = map[*from++]; } } /* transp_frame_data: copy the frame data into the actual image, using transparency occasionally according to a heuristic described below */ static void X(transp_frame_data)(Gif_Stream *gfs, Gif_Image *gfi, uint8_t *map, int optimize_flags, Gif_CompressInfo *gcinfo) { Gif_OptBounds ob = safe_bounds(gfi); int x, y, transparent = gfi->transparent; palindex_type *last = 0; palindex_type *cur = 0; uint8_t *data, *begin_same; uint8_t *t2_data = 0, *last_for_t2; int nsame; /* First, try w/o transparency. Compare this to the result using transparency and pick the better of the two. */ X(simple_frame_data)(gfi, map); Gif_FullCompressImage(gfs, gfi, gcinfo); gcinfo->flags |= GIF_WRITE_SHRINK; /* Actually copy data to frame. Use transparency if possible to shrink the size of the written GIF. The written GIF will be small if patterns (sequences of pixel values) recur in the image. We could conceivably use transparency to produce THE OPTIMAL image, with the most recurring patterns of the best kinds; but this would be very hard (wouldn't it?). Instead, we settle for a heuristic: we try and create RUNS. (Since we *try* to create them, they will presumably recur!) A RUN is a series of adjacent pixels all with the same value. By & large, we just use the regular image's values. However, we might create a transparent run *not in* the regular image, if TWO OR MORE adjacent runs OF DIFFERENT COLORS *could* be made transparent. (An area can be made transparent if the corresponding area in the previous frame had the same colors as the area does now.) Why? If only one run (say of color C) could be transparent, we get no large immediate advantage from making it transparent (it'll be a run of the same length regardless). Also, we might LOSE: what if the run was adjacent to some more of color C, which couldn't be made transparent? If we use color C (instead of the transparent color), then we get a longer run. This simple heuristic does a little better than Gifwizard's (6/97) on some images, but does *worse than nothing at all* on others. However, it DOES do better than the complicated, greedy algorithm that preceded it; and now we pick either the transparency-optimized version or the normal version, whichever compresses smaller, for the best of both worlds. (9/98) On several images, making SINGLE color runs transparent wins over the previous heuristic, so try both at optimize level 3 or above (the cost is ~30%). (2/11) */ data = begin_same = last_for_t2 = gfi->image_data; nsame = 0; for (y = 0; y < ob.height; ++y) { last = X(last_data) + (unsigned) screen_width * (y + ob.top) + ob.left; cur = X(this_data) + (unsigned) screen_width * (y + ob.top) + ob.left; for (x = 0; x < ob.width; ++x) { if (*cur != *last && map[*cur] != transparent) { if (nsame == 1 && data[-1] != transparent && (optimize_flags & GT_OPT_MASK) > 2) { if (!t2_data) t2_data = Gif_NewArray(uint8_t, (size_t) ob.width * (size_t) ob.height); memcpy(t2_data + (last_for_t2 - gfi->image_data), last_for_t2, begin_same - last_for_t2); memset(t2_data + (begin_same - gfi->image_data), transparent, data - begin_same); last_for_t2 = data; } nsame = 0; } else if (nsame == 0) { begin_same = data; ++nsame; } else if (nsame == 1 && map[*cur] != data[-1]) { memset(begin_same, transparent, data - begin_same); ++nsame; } if (nsame > 1) *data = transparent; else *data = map[*cur]; ++data, ++cur, ++last; } } if (t2_data) memcpy(t2_data + (last_for_t2 - gfi->image_data), last_for_t2, data - last_for_t2); /* Now, try compressed transparent version(s) and pick the better of the two (or three). */ Gif_FullCompressImage(gfs, gfi, gcinfo); if (t2_data) { Gif_SetUncompressedImage(gfi, t2_data, Gif_Free, 0); Gif_FullCompressImage(gfs, gfi, gcinfo); } Gif_ReleaseUncompressedImage(gfi); gcinfo->flags &= ~GIF_WRITE_SHRINK; } /***** * CREATE NEW IMAGE DATA **/ /* last == what last image ended up looking like this == what new image should look like last = apply O1 + dispose O1 + ... + apply On-1 + dispose On-1 this = apply U1 + dispose U1 + ... + apply Un-1 + dispose Un-1 + apply Un invariant: apply O1 + dispose O1 + ... + apply Ok === apply U1 + dispose U1 + ... + apply Uk */ static void X(create_new_image_data)(Gif_Stream *gfs, int optimize_flags) { Gif_Image cur_unopt_gfi; /* placeholder; maintains pre-optimization image size so we can apply background disposal */ unsigned screen_size = (unsigned) screen_width * (unsigned) screen_height; palindex_type *previous_data = 0; Gif_CompressInfo gcinfo = gif_write_info; if ((optimize_flags & GT_OPT_MASK) >= 3) gcinfo.flags |= GIF_WRITE_OPTIMIZE; gfs->global = out_global_map; /* do first image. Remember to uncompress it if necessary */ X(erase_screen)(X(last_data)); X(erase_screen)(X(this_data)); for (image_index = 0; image_index < gfs->nimages; image_index++) { Gif_Image *cur_gfi = gfs->images[image_index]; Gif_OptData *opt = (Gif_OptData *)cur_gfi->user_data; int was_compressed = (cur_gfi->img == 0); /* save previous data if necessary */ if (cur_gfi->disposal == GIF_DISPOSAL_PREVIOUS) { if (!previous_data) previous_data = Gif_NewArray(palindex_type, screen_size); X(copy_data_area)(previous_data, X(this_data), cur_gfi); } /* set up this_data to be equal to the current image */ X(apply_frame)(X(this_data), gfs, cur_gfi, 0, 0); /* save actual bounds and disposal from unoptimized version so we can apply the disposal correctly next time through */ cur_unopt_gfi = *cur_gfi; /* set bounds and disposal from optdata */ Gif_ReleaseUncompressedImage(cur_gfi); cur_gfi->left = opt->left; cur_gfi->top = opt->top; cur_gfi->width = opt->width; cur_gfi->height = opt->height; cur_gfi->disposal = opt->disposal; if (image_index > 0) cur_gfi->interlace = 0; /* find the new image's colormap and then make new data */ { uint8_t *map = prepare_colormap(cur_gfi, opt->needed_colors); uint8_t *data = Gif_NewArray(uint8_t, (size_t) cur_gfi->width * (size_t) cur_gfi->height); Gif_SetUncompressedImage(cur_gfi, data, Gif_Free, 0); /* don't use transparency on first frame */ if ((optimize_flags & GT_OPT_MASK) > 1 && image_index > 0 && cur_gfi->transparent >= 0) X(transp_frame_data)(gfs, cur_gfi, map, optimize_flags, &gcinfo); else X(simple_frame_data)(cur_gfi, map); if (cur_gfi->img) { if (was_compressed || (optimize_flags & GT_OPT_MASK) > 1) { Gif_FullCompressImage(gfs, cur_gfi, &gcinfo); Gif_ReleaseUncompressedImage(cur_gfi); } else /* bug fix 22.May.2001 */ Gif_ReleaseCompressedImage(cur_gfi); } Gif_DeleteArray(map); } delete_opt_data(opt); cur_gfi->user_data = 0; /* Set up last_data and this_data. last_data must contain this_data + new disposal. this_data must contain this_data + old disposal. */ if (cur_gfi->disposal == GIF_DISPOSAL_NONE || cur_gfi->disposal == GIF_DISPOSAL_ASIS) X(copy_data_area)(X(last_data), X(this_data), cur_gfi); else if (cur_gfi->disposal == GIF_DISPOSAL_BACKGROUND) X(erase_data_area)(X(last_data), cur_gfi); else if (cur_gfi->disposal != GIF_DISPOSAL_PREVIOUS) assert(0 && "optimized frame has strange disposal"); if (cur_unopt_gfi.disposal == GIF_DISPOSAL_BACKGROUND) X(erase_data_area)(X(this_data), &cur_unopt_gfi); else if (cur_unopt_gfi.disposal == GIF_DISPOSAL_PREVIOUS) X(copy_data_area)(X(this_data), previous_data, &cur_unopt_gfi); } if (previous_data) Gif_DeleteArray(previous_data); } /***** * INITIALIZATION AND FINALIZATION **/ static void X(finalize_optimizer_data)(void) { Gif_DeleteArray(X(last_data)); Gif_DeleteArray(X(this_data)); } gifsicle-1.96/src/quantize.c000066400000000000000000001766031475770763400160720ustar00rootroot00000000000000/* quantize.c - Histograms and quantization for gifsicle. Copyright (C) 1997-2025 Eddie Kohler, ekohler@gmail.com This file is part of gifsicle. Gifsicle is free software. It is distributed under the GNU Public License, version 2; you can copy, distribute, or alter it at will, as long as this notice is kept intact and this source code is made available. There is no warranty, express or implied. */ #include #include "gifsicle.h" #include "kcolor.h" #include #include #include #include #include #include /* COLORMAP FUNCTIONS return a palette (a vector of Gif_Colors). The pixel fields are undefined; the haspixel fields are all 0. */ typedef struct { int first; int size; uint32_t pixel; } adaptive_slot; Gif_Colormap* colormap_median_cut(kchist* kch, Gt_OutputData* od) { int adapt_size = od->colormap_size; adaptive_slot *slots = Gif_NewArray(adaptive_slot, adapt_size); Gif_Colormap *gfcm = Gif_NewFullColormap(adapt_size, 256); Gif_Color *adapt = gfcm->col; int nadapt; int i, j, k; /* This code was written with reference to ppmquant by Jef Poskanzer, part of the pbmplus package. */ if (adapt_size < 2 || adapt_size > 256) fatal_error("adaptive palette size must be between 2 and 256"); if (adapt_size >= kch->n && !od->colormap_fixed) warning(1, "trivial adaptive palette (only %d %s in source)", kch->n, kch->n == 1 ? "color" : "colors"); if (adapt_size >= kch->n) adapt_size = kch->n; /* 0. remove any transparent color from consideration; reduce adaptive palette size to accommodate transparency if it looks like that'll be necessary */ if (adapt_size > 2 && adapt_size < kch->n && kch->n <= 265 && od->colormap_needs_transparency) adapt_size--; /* 1. set up the first slot, containing all pixels. */ slots[0].first = 0; slots[0].size = kch->n; slots[0].pixel = 0; for (i = 0; i < kch->n; i++) slots[0].pixel += kch->h[i].count; /* 2. split slots until we have enough. */ for (nadapt = 1; nadapt < adapt_size; nadapt++) { adaptive_slot *split = 0; kcolor minc, maxc; kchistitem *slice; /* 2.1. pick the slot to split. */ { uint32_t split_pixel = 0; for (i = 0; i < nadapt; i++) if (slots[i].size >= 2 && slots[i].pixel > split_pixel) { split = &slots[i]; split_pixel = slots[i].pixel; } if (!split) break; } slice = &kch->h[split->first]; /* 2.2. find its extent. */ { kchistitem *trav = slice; minc = maxc = trav->ka.k; for (i = 1, trav++; i < split->size; i++, trav++) for (k = 0; k != 3; ++k) { minc.a[k] = min(minc.a[k], trav->ka.a[k]); maxc.a[k] = max(maxc.a[k], trav->ka.a[k]); } } /* 2.3. decide how to split it. use the luminance method. also sort the colors. */ { double red_diff = 0.299 * (maxc.a[0] - minc.a[0]); double green_diff = 0.587 * (maxc.a[1] - minc.a[1]); double blue_diff = 0.114 * (maxc.a[2] - minc.a[2]); if (red_diff >= green_diff && red_diff >= blue_diff) qsort(slice, split->size, sizeof(kchistitem), kchistitem_compare_red); else if (green_diff >= blue_diff) qsort(slice, split->size, sizeof(kchistitem), kchistitem_compare_green); else qsort(slice, split->size, sizeof(kchistitem), kchistitem_compare_blue); } /* 2.4. decide where to split the slot and split it there. */ { uint32_t half_pixels = split->pixel / 2; uint32_t pixel_accum = slice[0].count; uint32_t diff1, diff2; for (i = 1; i < split->size - 1 && pixel_accum < half_pixels; i++) pixel_accum += slice[i].count; /* We know the area before the split has more pixels than the area after, possibly by a large margin (bad news). If it would shrink the margin, change the split. */ diff1 = 2*pixel_accum - split->pixel; diff2 = split->pixel - 2*(pixel_accum - slice[i-1].count); if (diff2 < diff1 && i > 1) { i--; pixel_accum -= slice[i].count; } slots[nadapt].first = split->first + i; slots[nadapt].size = split->size - i; slots[nadapt].pixel = split->pixel - pixel_accum; split->size = i; split->pixel = pixel_accum; } } /* 3. make the new palette by choosing one color from each slot. */ for (i = 0; i < nadapt; i++) { double px[3]; kchistitem* slice = &kch->h[ slots[i].first ]; kcolor kc; px[0] = px[1] = px[2] = 0; for (j = 0; j != slots[i].size; ++j) for (k = 0; k != 3; ++k) px[k] += slice[j].ka.a[k] * (double) slice[j].count; kc.a[0] = (int) (px[0] / slots[i].pixel); kc.a[1] = (int) (px[1] / slots[i].pixel); kc.a[2] = (int) (px[2] / slots[i].pixel); adapt[i] = kc_togfcg(kc); } Gif_DeleteArray(slots); gfcm->ncol = nadapt; return gfcm; } void kcdiversity_init(kcdiversity* div, kchist* kch, int dodither) { int i; div->kch = kch; qsort(kch->h, kch->n, sizeof(kchistitem), kchistitem_compare_popularity); div->closest = Gif_NewArray(int, kch->n); div->min_dist = Gif_NewArray(uint32_t, kch->n); for (i = 0; i != kch->n; ++i) div->min_dist[i] = (uint32_t) -1; if (dodither) { div->min_dither_dist = Gif_NewArray(uint32_t, kch->n); for (i = 0; i != kch->n; ++i) div->min_dither_dist[i] = (uint32_t) -1; } else div->min_dither_dist = NULL; div->chosen = Gif_NewArray(int, kch->n); div->nchosen = 0; } void kcdiversity_cleanup(kcdiversity* div) { Gif_DeleteArray(div->closest); Gif_DeleteArray(div->min_dist); Gif_DeleteArray(div->min_dither_dist); Gif_DeleteArray(div->chosen); } int kcdiversity_find_popular(kcdiversity* div) { int i, n = div->kch->n; for (i = 0; i != n && div->min_dist[i] == 0; ++i) /* spin */; return i; } int kcdiversity_find_diverse(kcdiversity* div, double ditherweight) { int i, n = div->kch->n, chosen = kcdiversity_find_popular(div); if (chosen == n) /* skip */; else if (!ditherweight || !div->min_dither_dist) { for (i = chosen + 1; i != n; ++i) if (div->min_dist[i] > div->min_dist[chosen]) chosen = i; } else { double max_dist = div->min_dist[chosen] + ditherweight * div->min_dither_dist[chosen]; for (i = chosen + 1; i != n; ++i) if (div->min_dist[i] != 0) { double dist = div->min_dist[i] + ditherweight * div->min_dither_dist[i]; if (dist > max_dist) { chosen = i; max_dist = dist; } } } return chosen; } int kcdiversity_choose(kcdiversity* div, int chosen, int dodither) { int i, j, k, n = div->kch->n; kchistitem* hist = div->kch->h; div->min_dist[chosen] = 0; if (div->min_dither_dist) div->min_dither_dist[chosen] = 0; div->closest[chosen] = chosen; /* adjust the min_dist array */ for (i = 0; i != n; ++i) if (div->min_dist[i]) { uint32_t dist = kc_distance(hist[i].ka.k, hist[chosen].ka.k); if (dist < div->min_dist[i]) { div->min_dist[i] = dist; div->closest[i] = chosen; } } /* also account for dither distances */ if (dodither && div->min_dither_dist) for (i = 0; i != div->nchosen; ++i) { kcolor x = hist[chosen].ka.k, *y = &hist[div->chosen[i]].ka.k; /* penalize combinations with large luminance difference */ double dL = abs(kc_luminance(x) - kc_luminance(*y)); dL = (dL > 8192 ? dL * 4 / 32767. : 1); /* create combination */ for (k = 0; k != 3; ++k) x.a[k] = (x.a[k] + y->a[k]) >> 1; /* track closeness of combination to other colors */ for (j = 0; j != n; ++j) if (div->min_dist[j]) { double dist = kc_distance(hist[j].ka.k, x) * dL; if (dist < div->min_dither_dist[j]) div->min_dither_dist[j] = (uint32_t) dist; } } div->chosen[div->nchosen] = chosen; ++div->nchosen; return chosen; } static void colormap_diversity_do_blend(kcdiversity* div) { int i, j, k, n = div->kch->n; kchistitem* hist = div->kch->h; int* chosenmap = Gif_NewArray(int, n); scale_color* di = Gif_NewArray(scale_color, div->nchosen); for (i = 0; i != div->nchosen; ++i) for (k = 0; k != 4; ++k) di[i].a[k] = 0; for (i = 0; i != div->nchosen; ++i) chosenmap[div->chosen[i]] = i; for (i = 0; i != n; ++i) { double count = hist[i].count; if (div->closest[i] == i) count *= 3; j = chosenmap[div->closest[i]]; for (k = 0; k != 3; ++k) di[j].a[k] += hist[i].ka.a[k] * count; di[j].a[3] += count; } for (i = 0; i != div->nchosen; ++i) { int match = div->chosen[i]; if (di[i].a[3] >= 5 * hist[match].count) for (k = 0; k != 3; ++k) hist[match].ka.a[k] = (int) (di[i].a[k] / di[i].a[3]); } Gif_DeleteArray(chosenmap); Gif_DeleteArray(di); } static Gif_Colormap * colormap_diversity(kchist* kch, Gt_OutputData* od, int blend) { int adapt_size = od->colormap_size; kcdiversity div; Gif_Colormap* gfcm = Gif_NewFullColormap(adapt_size, 256); int nadapt = 0; int chosen; /* This code uses XV's modified diversity algorithm, and was written with reference to XV's implementation of that algorithm by John Bradley and Tom Lane . */ if (adapt_size < 2 || adapt_size > 256) fatal_error("adaptive palette size must be between 2 and 256"); if (adapt_size > kch->n && !od->colormap_fixed) warning(1, "trivial adaptive palette (only %d colors in source)", kch->n); if (adapt_size > kch->n) adapt_size = kch->n; /* 0. remove any transparent color from consideration; reduce adaptive palette size to accommodate transparency if it looks like that'll be necessary */ /* It will be necessary to accommodate transparency if (1) there is transparency in the image; (2) the adaptive palette isn't trivial; and (3) there are a small number of colors in the image (arbitrary constant: <= 265), so it's likely that most images will use most of the slots, so it's likely there won't be unused slots. */ if (adapt_size > 2 && adapt_size < kch->n && kch->n <= 265 && od->colormap_needs_transparency) adapt_size--; /* blending has bad effects when there are very few colors */ if (adapt_size < 4) blend = 0; /* 1. initialize min_dist and sort the colors in order of popularity. */ kcdiversity_init(&div, kch, od->dither_type != dither_none); /* 2. choose colors one at a time */ for (nadapt = 0; nadapt < adapt_size; nadapt++) { /* 2.1. choose the color to be added */ if (nadapt == 0 || (nadapt >= 10 && nadapt % 2 == 0)) /* 2.1a. want most popular unchosen color */ chosen = kcdiversity_find_popular(&div); else if (od->dither_type == dither_none) /* 2.1b. choose based on diversity from unchosen colors */ chosen = kcdiversity_find_diverse(&div, 0); else { /* 2.1c. choose based on diversity from unchosen colors, but allow dithered combinations to stand in for colors, particularly early on in the color finding process */ /* Weight assigned to dithered combinations drops as we proceed. */ #if HAVE_POW double ditherweight = 0.05 + pow(0.25, 1 + (nadapt - 1) / 3.); #else double ditherweight = nadapt < 4 ? 0.25 : 0.125; #endif chosen = kcdiversity_find_diverse(&div, ditherweight); } kcdiversity_choose(&div, chosen, od->dither_type != dither_none && nadapt > 0 && nadapt < 64); } /* 3. make the new palette by choosing one color from each slot. */ if (blend) colormap_diversity_do_blend(&div); for (nadapt = 0; nadapt != div.nchosen; ++nadapt) gfcm->col[nadapt] = kc_togfcg(kch->h[div.chosen[nadapt]].ka.k); gfcm->ncol = nadapt; kcdiversity_cleanup(&div); return gfcm; } Gif_Colormap* colormap_blend_diversity(kchist* kch, Gt_OutputData* od) { return colormap_diversity(kch, od, 1); } Gif_Colormap* colormap_flat_diversity(kchist* kch, Gt_OutputData* od) { return colormap_diversity(kch, od, 0); } void colormap_image_posterize(Gif_Image *gfi, uint8_t *new_data, Gif_Colormap *old_cm, kd3_tree* kd3, uint32_t *histogram) { int ncol = old_cm->ncol; Gif_Color *col = old_cm->col; int map[256]; int i, j; int transparent = gfi->transparent; /* find closest colors in new colormap */ assert(old_cm->capacity >= 256); for (i = 0; i < ncol; ++i) { map[i] = col[i].pixel = kd3_closest8g(kd3, col[i].gfc_red, col[i].gfc_green, col[i].gfc_blue); col[i].haspixel = 1; } for (i = ncol; i < 256; ++i) { map[i] = col[i].pixel = 0; col[i].haspixel = 1; } /* map image */ for (j = 0; j < gfi->height; j++) { uint8_t *data = gfi->img[j]; for (i = 0; i < gfi->width; i++, data++, new_data++) if (*data != transparent) { *new_data = map[*data]; histogram[*new_data]++; } } } #define DITHER_SCALE 1024 #define DITHER_SHIFT 10 #define DITHER_SCALE_M1 (DITHER_SCALE-1) #define DITHER_ITEM2ERR (1<<(DITHER_SHIFT-7)) #define N_RANDOM_VALUES 512 void colormap_image_floyd_steinberg(Gif_Image *gfi, uint8_t *all_new_data, Gif_Colormap *old_cm, kd3_tree* kd3, uint32_t *histogram) { static int32_t *random_values = 0; int width = gfi->width; int dither_direction = 0; int transparent = gfi->transparent; int i, j, k; kcolor *old_kc; wkcolor *err, *err1; /* Initialize distances; beware uninitialized colors */ assert(old_cm->capacity >= 256); old_kc = Gif_NewArray(kcolor, old_cm->capacity); for (i = 0; i < old_cm->ncol; ++i) { Gif_Color* c = &old_cm->col[i]; old_kc[i] = kd3_makegfcg(kd3, c); c->pixel = kd3_closest_transformed(kd3, old_kc[i], NULL); c->haspixel = 1; } for (i = old_cm->ncol; i < 256; ++i) { Gif_Color* c = &old_cm->col[i]; old_kc[i] = kd3_makegfcg(kd3, c); c->pixel = 0; c->haspixel = 1; } /* This code was written with reference to ppmquant by Jef Poskanzer, part of the pbmplus package. */ /* Initialize Floyd-Steinberg error vectors to small random values, so we don't get artifacts on the top row */ err = Gif_NewArray(wkcolor, width + 2); err1 = Gif_NewArray(wkcolor, width + 2); /* Use the same random values on each call in an attempt to minimize "jumping dithering" effects on animations */ if (!random_values) { random_values = Gif_NewArray(int32_t, N_RANDOM_VALUES); for (i = 0; i < N_RANDOM_VALUES; i++) random_values[i] = RANDOM() % (DITHER_SCALE_M1 * 2) - DITHER_SCALE_M1; } for (i = 0; i < gfi->width + 2; i++) { j = (i + gfi->left) * 3; for (k = 0; k < 3; ++k) err[i].a[k] = random_values[ (j + k) % N_RANDOM_VALUES ]; } /* err1 initialized below */ kd3_build_xradius(kd3); /* Do the image! */ for (j = 0; j < gfi->height; j++) { int d0, d1, d2, d3; /* used for error diffusion */ uint8_t *data, *new_data; int x; if (dither_direction) { x = width - 1; d0 = 0, d1 = 2, d2 = 1, d3 = 0; } else { x = 0; d0 = 2, d1 = 0, d2 = 1, d3 = 2; } data = &gfi->img[j][x]; new_data = all_new_data + j * (unsigned) width + x; for (i = 0; i < width + 2; i++) err1[i].a[0] = err1[i].a[1] = err1[i].a[2] = 0; /* Do a single row */ while (x >= 0 && x < width) { int e; kcolor use; /* the transparent color never gets adjusted */ if (*data == transparent) goto next; /* find desired new color */ use = old_kc[*data]; /* use Floyd-Steinberg errors to adjust */ for (k = 0; k < 3; ++k) { int v = use.a[k] + (err[x+1].a[k] & ~(DITHER_ITEM2ERR-1)) / DITHER_ITEM2ERR; use.a[k] = KC_CLAMPV(v); } e = old_cm->col[*data].pixel; if (kc_distance(kd3->ks[e], use) < kd3->xradius[e]) *new_data = e; else *new_data = kd3_closest_transformed(kd3, use, NULL); histogram[*new_data]++; /* calculate and propagate the error between desired and selected color. Assume that, with a large scale (1024), we don't need to worry about image artifacts caused by error accumulation (the fact that the error terms might not sum to the error). */ for (k = 0; k < 3; ++k) { e = (use.a[k] - kd3->ks[*new_data].a[k]) * DITHER_ITEM2ERR; if (e) { err [x+d0].a[k] += ((e * 7) & ~15) / 16; err1[x+d1].a[k] += ((e * 3) & ~15) / 16; err1[x+d2].a[k] += ((e * 5) & ~15) / 16; err1[x+d3].a[k] += ( e & ~15) / 16; } } next: if (dither_direction) x--, data--, new_data--; else x++, data++, new_data++; } /* Did a single row */ /* change dithering directions */ { wkcolor *temp = err1; err1 = err; err = temp; dither_direction = !dither_direction; } } /* delete temporary storage */ Gif_DeleteArray(err); Gif_DeleteArray(err1); Gif_DeleteArray(old_kc); } /* This function is a variant of the colormap_image_floyd_steinberg function since the Atkinson dither is a variant of the Floyd-Steinberg dither. */ void colormap_image_atkinson(Gif_Image *gfi, uint8_t *all_new_data, Gif_Colormap *old_cm, kd3_tree* kd3, uint32_t *histogram) { static int32_t *random_values = 0; int transparent = gfi->transparent; int i, j, k; kcolor *old_kc; wkcolor *err[3]; /* Initialize distances; beware uninitialized colors */ assert(old_cm->capacity >= 256); old_kc = Gif_NewArray(kcolor, old_cm->capacity); for (i = 0; i < old_cm->ncol; ++i) { Gif_Color* c = &old_cm->col[i]; old_kc[i] = kd3_makegfcg(kd3, c); c->pixel = kd3_closest_transformed(kd3, old_kc[i], NULL); c->haspixel = 1; } for (i = old_cm->ncol; i < 256; ++i) { Gif_Color* c = &old_cm->col[i]; old_kc[i] = kd3_makegfcg(kd3, c); c->pixel = 0; c->haspixel = 1; } /* Initialize Atkinson error vectors */ for (i = 0; i < 3; i++) err[i] = Gif_NewArray(wkcolor, gfi->width + 2); /* Use the same random values on each call in an attempt to minimize "jumping dithering" effects on animations */ if (!random_values) { random_values = Gif_NewArray(int32_t, N_RANDOM_VALUES); for (i = 0; i < N_RANDOM_VALUES; i++) random_values[i] = RANDOM() % (DITHER_SCALE_M1 * 2) - DITHER_SCALE_M1; } for (i = 0; i < gfi->width + 2; i++) { j = (i + gfi->left) * 3; for (k = 0; k < 3; ++k) err[0][i].a[k] = random_values[ (j + k) % N_RANDOM_VALUES ]; } kd3_build_xradius(kd3); /* Do the image! */ for (j = 0; j < gfi->height; j++) { uint8_t *data = &gfi->img[j][0]; uint8_t *new_data = all_new_data + j * (unsigned) gfi->width; /* Initialize error rows for this pass */ for (i = 0; i < gfi->width + 2; i++) for (k = 0; k < 3; k++) err[1][i].a[k] = err[2][i].a[k] = 0; for (int x = 0; x < gfi->width; x++, data++, new_data++) { int e; kcolor use; /* Transparent color never gets adjusted, skip */ if (*data == transparent) { continue; } /* Calculate desired new color including current error */ use = old_kc[*data]; for (k = 0; k < 3; ++k) use.a[k] = KC_CLAMPV(use.a[k] + err[0][x].a[k] / DITHER_SCALE); /* Find the closest color in the colormap */ e = old_cm->col[*data].pixel; if (kc_distance(kd3->ks[e], use) < kd3->xradius[e]) *new_data = e; else *new_data = kd3_closest_transformed(kd3, use, NULL); histogram[*new_data]++; /* Calculate and propagate the error using the Atkinson dithering algorithm */ for (k = 0; k < 3; ++k) { int e = (use.a[k] - kd3->ks[*new_data].a[k]) * DITHER_SCALE; if (x + 1 < gfi->width) err[0][x + 1].a[k] += e / 8; if (x + 2 < gfi->width) err[0][x + 2].a[k] += e / 8; if (j + 1 < gfi->height) { err[1][x].a[k] += e / 8; err[1][x + 1].a[k] += e / 8; if (x + 1 < gfi->width) err[1][x + 1].a[k] += e / 8; } if (j + 2 < gfi->height) err[2][x].a[k] += e / 8; } } /* Rotate error buffers */ wkcolor *temp = err[0]; err[0] = err[1]; err[1] = err[2]; err[2] = temp; } /* Delete temporary storage */ for (i = 0; i < 3; i++) { Gif_DeleteArray(err[i]); } Gif_DeleteArray(old_kc); } typedef struct odselect_planitem { uint8_t plan; uint16_t frac; } odselect_planitem; static int* ordered_dither_lum; static void plan_from_cplan(uint8_t* plan, int nplan, const odselect_planitem* cp, int ncp, int whole) { int i, cfrac_subt = 0, planpos = 0, end_planpos; for (i = 0; i != ncp; ++i) { cfrac_subt += cp[i].frac; end_planpos = cfrac_subt * nplan / whole; while (planpos != end_planpos) plan[planpos++] = cp[i].plan; } assert(planpos == nplan); } static int ordered_dither_plan_compare(const void* xa, const void* xb) { const uint8_t* a = (const uint8_t*) xa; const uint8_t* b = (const uint8_t*) xb; if (ordered_dither_lum[*a] != ordered_dither_lum[*b]) return ordered_dither_lum[*a] - ordered_dither_lum[*b]; else return *a - *b; } static int kc_line_closest(kcolor p0, kcolor p1, kcolor ref, double* t, unsigned* dist) { wkcolor p01, p0ref; kcolor online; unsigned den; int d; for (d = 0; d != 3; ++d) { p01.a[d] = p1.a[d] - p0.a[d]; p0ref.a[d] = ref.a[d] - p0.a[d]; } den = (unsigned) (p01.a[0]*p01.a[0] + p01.a[1]*p01.a[1] + p01.a[2]*p01.a[2]); if (den == 0) return 0; /* NB: We've run out of bits of precision. We can calculate the denominator in unsigned arithmetic, but the numerator might be negative, or it might be so large that it is unsigned. Calculate the numerator as a double. */ *t = ((double) p01.a[0]*p0ref.a[0] + p01.a[1]*p0ref.a[1] + p01.a[2]*p0ref.a[2]) / den; if (*t < 0 || *t > 1) return 0; for (d = 0; d != 3; ++d) { int v = (int) (p01.a[d] * *t) + p0.a[d]; online.a[d] = KC_CLAMPV(v); } *dist = kc_distance(online, ref); return 1; } static int kc_plane_closest(kcolor p0, kcolor p1, kcolor p2, kcolor ref, double* t, unsigned* dist) { wkcolor p0ref, p01, p02; double n[3], pvec[3], det, qvec[3], u, v; int d; /* Calculate the non-unit normal of the plane determined by the input colors (p0-p2) */ for (d = 0; d != 3; ++d) { p0ref.a[d] = ref.a[d] - p0.a[d]; p01.a[d] = p1.a[d] - p0.a[d]; p02.a[d] = p2.a[d] - p0.a[d]; } n[0] = p01.a[1]*p02.a[2] - p01.a[2]*p02.a[1]; n[1] = p01.a[2]*p02.a[0] - p01.a[0]*p02.a[2]; n[2] = p01.a[0]*p02.a[1] - p01.a[1]*p02.a[0]; /* Moeller-Trumbore ray tracing algorithm: trace a ray from `ref` along normal `n`; convert to barycentric coordinates to see if the ray intersects with the triangle. */ pvec[0] = n[1]*p02.a[2] - n[2]*p02.a[1]; pvec[1] = n[2]*p02.a[0] - n[0]*p02.a[2]; pvec[2] = n[0]*p02.a[1] - n[1]*p02.a[0]; det = pvec[0]*p01.a[0] + pvec[1]*p01.a[1] + pvec[2]*p01.a[2]; if (fabs(det) <= 0.0001220703125) /* optimizer will take care of that */ return 0; det = 1 / det; u = (p0ref.a[0]*pvec[0] + p0ref.a[1]*pvec[1] + p0ref.a[2]*pvec[2]) * det; if (u < 0 || u > 1) return 0; qvec[0] = p0ref.a[1]*p01.a[2] - p0ref.a[2]*p01.a[1]; qvec[1] = p0ref.a[2]*p01.a[0] - p0ref.a[0]*p01.a[2]; qvec[2] = p0ref.a[0]*p01.a[1] - p0ref.a[1]*p01.a[0]; v = (n[0]*qvec[0] + n[1]*qvec[1] + n[2]*qvec[2]) * det; if (v < 0 || v > 1 || u + v > 1) return 0; /* Now we know at there is a point in the triangle that is closer to `ref` than any point along its edges. Return the barycentric coordinates for that point and the distance to that point. */ t[0] = u; t[1] = v; v = (p02.a[0]*qvec[0] + p02.a[1]*qvec[1] + p02.a[2]*qvec[2]) * det; *dist = (unsigned) (v * v * (n[0]*n[0] + n[1]*n[1] + n[2]*n[2]) + 0.5); return 1; } static void limit_ordered_dither_plan(uint8_t* plan, int nplan, int nc, kcolor want, kd3_tree* kd3) { unsigned mindist, dist; int ncp = 0, nbestcp = 0, i, j, k; double t[2]; odselect_planitem cp[256], bestcp[16]; nc = nc <= 16 ? nc : 16; /* sort colors */ cp[0].plan = plan[0]; cp[0].frac = 1; for (ncp = i = 1; i != nplan; ++i) if (plan[i - 1] == plan[i]) ++cp[ncp - 1].frac; else { cp[ncp].plan = plan[i]; cp[ncp].frac = 1; ++ncp; } /* calculate plan */ mindist = (unsigned) -1; for (i = 0; i != ncp; ++i) { /* check for closest single color */ dist = kc_distance(kd3->ks[cp[i].plan], want); if (dist < mindist) { bestcp[0].plan = cp[i].plan; bestcp[0].frac = KC_WHOLE; nbestcp = 1; mindist = dist; } for (j = i + 1; nc >= 2 && j < ncp; ++j) { /* check for closest blend of two colors */ if (kc_line_closest(kd3->ks[cp[i].plan], kd3->ks[cp[j].plan], want, &t[0], &dist) && dist < mindist) { bestcp[0].plan = cp[i].plan; bestcp[1].plan = cp[j].plan; bestcp[1].frac = (int) (KC_WHOLE * t[0]); bestcp[0].frac = KC_WHOLE - bestcp[1].frac; nbestcp = 2; mindist = dist; } for (k = j + 1; nc >= 3 && k < ncp; ++k) /* check for closest blend of three colors */ if (kc_plane_closest(kd3->ks[cp[i].plan], kd3->ks[cp[j].plan], kd3->ks[cp[k].plan], want, &t[0], &dist) && dist < mindist) { bestcp[0].plan = cp[i].plan; bestcp[1].plan = cp[j].plan; bestcp[1].frac = (int) (KC_WHOLE * t[0]); bestcp[2].plan = cp[k].plan; bestcp[2].frac = (int) (KC_WHOLE * t[1]); bestcp[0].frac = KC_WHOLE - bestcp[1].frac - bestcp[2].frac; nbestcp = 3; mindist = dist; } } } plan_from_cplan(plan, nplan, bestcp, nbestcp, KC_WHOLE); } static void set_ordered_dither_plan(uint8_t* plan, int nplan, int nc, Gif_Color* gfc, kd3_tree* kd3) { kcolor want, cur; wkcolor err = wkc_zero(); int i, d; want = kd3_makegfcg(kd3, gfc); for (i = 0; i != nplan; ++i) { for (d = 0; d != 3; ++d) { int v = want.a[d] + err.a[d]; cur.a[d] = KC_CLAMPV(v); } plan[i] = kd3_closest_transformed(kd3, cur, NULL); for (d = 0; d != 3; ++d) err.a[d] += want.a[d] - kd3->ks[plan[i]].a[d]; } qsort(plan, nplan, 1, ordered_dither_plan_compare); if (nc < nplan && plan[0] != plan[nplan-1]) { int ncp = 1; for (i = 1; i != nplan; ++i) ncp += plan[i-1] != plan[i]; if (ncp > nc) limit_ordered_dither_plan(plan, nplan, nc, want, kd3); } gfc->haspixel = 1; } static void pow2_ordered_dither(Gif_Image* gfi, uint8_t* all_new_data, Gif_Colormap* old_cm, kd3_tree* kd3, uint32_t* histogram, const uint8_t* matrix, uint8_t* plan) { int mws, nplans, i, x, y; for (mws = 0; (1 << mws) != matrix[0]; ++mws) /* nada */; for (nplans = 0; (1 << nplans) != matrix[2]; ++nplans) /* nada */; for (y = 0; y != gfi->height; ++y) { uint8_t *data, *new_data, *thisplan; data = gfi->img[y]; new_data = all_new_data + y * (unsigned) gfi->width; for (x = 0; x != gfi->width; ++x) /* the transparent color never gets adjusted */ if (data[x] != gfi->transparent) { thisplan = &plan[data[x] << nplans]; if (!old_cm->col[data[x]].haspixel) set_ordered_dither_plan(thisplan, 1 << nplans, matrix[3], &old_cm->col[data[x]], kd3); i = matrix[4 + ((x + gfi->left) & (matrix[0] - 1)) + (((y + gfi->top) & (matrix[1] - 1)) << mws)]; new_data[x] = thisplan[i]; histogram[new_data[x]]++; } } } static void colormap_image_ordered(Gif_Image* gfi, uint8_t* all_new_data, Gif_Colormap* old_cm, kd3_tree* kd3, uint32_t* histogram, const uint8_t* matrix) { int mw = matrix[0], mh = matrix[1], nplan = matrix[2]; uint8_t* plan = Gif_NewArray(uint8_t, nplan * old_cm->ncol); int i, x, y; /* Written with reference to Joel Ylilouma's versions. */ /* Initialize colors */ assert(old_cm->capacity >= 256); for (i = 0; i != 256; ++i) old_cm->col[i].haspixel = 0; /* Initialize luminances, create luminance sorter */ ordered_dither_lum = Gif_NewArray(int, kd3->nitems); for (i = 0; i != kd3->nitems; ++i) ordered_dither_lum[i] = kc_luminance(kd3->ks[i]); /* Do the image! */ if ((mw & (mw - 1)) == 0 && (mh & (mh - 1)) == 0 && (nplan & (nplan - 1)) == 0) pow2_ordered_dither(gfi, all_new_data, old_cm, kd3, histogram, matrix, plan); else for (y = 0; y != gfi->height; ++y) { uint8_t *data, *new_data, *thisplan; data = gfi->img[y]; new_data = all_new_data + y * (unsigned) gfi->width; for (x = 0; x != gfi->width; ++x) /* the transparent color never gets adjusted */ if (data[x] != gfi->transparent) { thisplan = &plan[nplan * data[x]]; if (!old_cm->col[data[x]].haspixel) set_ordered_dither_plan(thisplan, nplan, matrix[3], &old_cm->col[data[x]], kd3); i = matrix[4 + (x + gfi->left) % mw + ((y + gfi->top) % mh) * mw]; new_data[x] = thisplan[i]; histogram[new_data[x]]++; } } /* delete temporary storage */ Gif_DeleteArray(ordered_dither_lum); Gif_DeleteArray(plan); } static void dither(Gif_Image* gfi, uint8_t* new_data, Gif_Colormap* old_cm, kd3_tree* kd3, uint32_t* histogram, Gt_OutputData* od) { if (od->dither_type == dither_default || od->dither_type == dither_floyd_steinberg) colormap_image_floyd_steinberg(gfi, new_data, old_cm, kd3, histogram); else if (od->dither_type == dither_ordered || od->dither_type == dither_ordered_new) colormap_image_ordered(gfi, new_data, old_cm, kd3, histogram, od->dither_data); else if (od->dither_type == dither_atkinson) colormap_image_atkinson(gfi, new_data, old_cm, kd3, histogram); else colormap_image_posterize(gfi, new_data, old_cm, kd3, histogram); } /* return value 1 means run the dither again */ static int try_assign_transparency(Gif_Image *gfi, Gif_Colormap *old_cm, uint8_t *new_data, Gif_Colormap *new_cm, int *new_ncol, kd3_tree* kd3, uint32_t *histogram) { uint32_t min_used; int i, j; int transparent = gfi->transparent; int new_transparent = -1; Gif_Color transp_value; if (transparent < 0) return 0; if (old_cm) transp_value = old_cm->col[transparent]; else GIF_SETCOLOR(&transp_value, 0, 0, 0); /* look for an unused pixel in the existing colormap; prefer the same color we had */ for (i = 0; i < *new_ncol; i++) if (histogram[i] == 0 && GIF_COLOREQ(&transp_value, &new_cm->col[i])) { new_transparent = i; goto found; } for (i = 0; i < *new_ncol; i++) if (histogram[i] == 0) { new_transparent = i; goto found; } /* try to expand the colormap */ if (*new_ncol < 256) { assert(*new_ncol < new_cm->capacity); new_transparent = *new_ncol; new_cm->col[new_transparent] = transp_value; (*new_ncol)++; goto found; } /* not found: mark the least-frequently-used color as the new transparent color and return 1 (meaning 'dither again') */ assert(*new_ncol == 256); min_used = 0xFFFFFFFFU; for (i = 0; i < 256; i++) if (histogram[i] < min_used) { new_transparent = i; min_used = histogram[i]; } kd3_disable(kd3, new_transparent); /* mark it as unusable */ return 1; found: for (j = 0; j < gfi->height; j++) { uint8_t *data = gfi->img[j]; for (i = 0; i < gfi->width; i++, data++, new_data++) if (*data == transparent) *new_data = new_transparent; } gfi->transparent = new_transparent; return 0; } static int color_compare_popularity(const void* va, const void* vb) { const Gif_Color* a = (const Gif_Color*) va; const Gif_Color* b = (const Gif_Color*) vb; return a->pixel > b->pixel ? -1 : a->pixel != b->pixel; } void colormap_stream(Gif_Stream* gfs, Gif_Colormap* new_cm, Gt_OutputData* od, int allow_shrink) { kd3_tree kd3; Gif_Color *new_col = new_cm->col; int new_ncol = new_cm->ncol, new_gray; int imagei, j; assert(new_cm->capacity >= 256); /* new_col[j].pixel == number of pixels with color j in the new image. */ for (j = 0; j < 256; j++) new_col[j].pixel = 0; /* initialize kd3 tree */ new_gray = 1; for (j = 0; new_gray && j < new_cm->ncol; ++j) { if (new_col[j].gfc_red != new_col[j].gfc_green || new_col[j].gfc_red != new_col[j].gfc_blue) new_gray = 0; } if (new_gray) { kd3_init_build(&kd3, kc_luminance_transform, new_cm); } else if (od->colormap_gamma_type == KC_GAMMA_OKLAB) { kd3_init_build(&kd3, kc_oklab_transform, new_cm); } else { kd3_init_build(&kd3, NULL, new_cm); } for (imagei = 0; imagei < gfs->nimages; imagei++) { Gif_Image *gfi = gfs->images[imagei]; Gif_Colormap *gfcm = gfi->local ? gfi->local : gfs->global; int only_compressed = (gfi->img == 0); if (gfcm) { /* If there was an old colormap, change the image data */ uint8_t *new_data = Gif_NewArray(uint8_t, (unsigned) gfi->width * (unsigned) gfi->height); uint32_t histogram[256]; unmark_colors(new_cm); unmark_colors(gfcm); if (only_compressed) Gif_UncompressImage(gfs, gfi); kd3_enable_all(&kd3); do { for (j = 0; j < 256; j++) histogram[j] = 0; dither(gfi, new_data, gfcm, &kd3, histogram, od); } while (try_assign_transparency(gfi, gfcm, new_data, new_cm, &new_ncol, &kd3, histogram)); Gif_ReleaseUncompressedImage(gfi); /* version 1.28 bug fix: release any compressed version or it'll cause bad images */ Gif_ReleaseCompressedImage(gfi); Gif_SetUncompressedImage(gfi, new_data, Gif_Free, 0); /* update count of used colors */ for (j = 0; j < 256; j++) new_col[j].pixel += histogram[j]; if (gfi->transparent >= 0) /* we don't have data on the number of used colors for transparency so fudge it. */ new_col[gfi->transparent].pixel += (unsigned) gfi->width * (unsigned) gfi->height / 8; } else { /* Can't compress new_cm afterwards if we didn't actively change colors over */ allow_shrink = 0; } if (gfi->local) { Gif_DeleteColormap(gfi->local); gfi->local = 0; } /* 1.92: recompress *after* deleting the local colormap */ if (gfcm && only_compressed) { Gif_FullCompressImage(gfs, gfi, &gif_write_info); Gif_ReleaseUncompressedImage(gfi); } } /* Set new_cm->ncol from new_ncol. We didn't update new_cm->ncol before so the closest-color algorithms wouldn't see any new transparent colors. That way added transparent colors were only used for transparency. */ new_cm->ncol = new_ncol; /* change the background. I hate the background by now */ if ((gfs->nimages == 0 || gfs->images[0]->transparent < 0) && gfs->global && gfs->background < gfs->global->ncol) { Gif_Color *c = &gfs->global->col[gfs->background]; gfs->background = kd3_closest8g(&kd3, c->gfc_red, c->gfc_green, c->gfc_blue); new_col[gfs->background].pixel++; } else if (gfs->nimages > 0 && gfs->images[0]->transparent >= 0) gfs->background = gfs->images[0]->transparent; else gfs->background = 0; Gif_DeleteColormap(gfs->global); kd3_cleanup(&kd3); /* We may have used only a subset of the colors in new_cm. We try to store only that subset, just as if we'd piped the output of 'gifsicle --use-colormap=X' through 'gifsicle' another time. */ gfs->global = Gif_CopyColormap(new_cm); for (j = 0; j < new_cm->ncol; ++j) gfs->global->col[j].haspixel = 0; if (allow_shrink) { /* only bother to recompress if we'll get anything out of it */ allow_shrink = 0; for (j = 0; j < new_cm->ncol - 1; j++) if (new_col[j].pixel == 0 || new_col[j].pixel < new_col[j+1].pixel) { allow_shrink = 1; break; } } if (allow_shrink) { int map[256]; /* Gif_CopyColormap copies the 'pixel' values as well */ new_col = gfs->global->col; for (j = 0; j < new_cm->ncol; j++) new_col[j].haspixel = j; /* sort based on popularity */ qsort(new_col, new_cm->ncol, sizeof(Gif_Color), color_compare_popularity); /* set up the map and reduce the number of colors */ for (j = 0; j < new_cm->ncol; j++) map[ new_col[j].haspixel ] = j; for (j = 0; j < new_cm->ncol; j++) if (!new_col[j].pixel) { gfs->global->ncol = j; break; } /* map the image data, transparencies, and background */ if (gfs->background < gfs->global->ncol) gfs->background = map[gfs->background]; for (imagei = 0; imagei < gfs->nimages; imagei++) { Gif_Image *gfi = gfs->images[imagei]; int only_compressed = (gfi->img == 0); uint32_t size; uint8_t *data; if (only_compressed) Gif_UncompressImage(gfs, gfi); data = gfi->image_data; for (size = (unsigned) gfi->width * (unsigned) gfi->height; size > 0; size--, data++) *data = map[*data]; if (gfi->transparent >= 0) gfi->transparent = map[gfi->transparent]; if (only_compressed) { Gif_FullCompressImage(gfs, gfi, &gif_write_info); Gif_ReleaseUncompressedImage(gfi); } } } } /* Halftone algorithms */ static const uint8_t dither_matrix_o3x3[4 + 3*3] = { 3, 3, 9, 9, 2, 6, 3, 5, 0, 8, 1, 7, 4 }; static const uint8_t dither_matrix_o4x4[4 + 4*4] = { 4, 4, 16, 16, 0, 8, 3, 10, 12, 4, 14, 6, 2, 11, 1, 9, 15, 7, 13, 5 }; static const uint8_t dither_matrix_o8x8[4 + 8*8] = { 8, 8, 64, 64, 0, 48, 12, 60, 3, 51, 15, 63, 32, 16, 44, 28, 35, 19, 47, 31, 8, 56, 4, 52, 11, 59, 7, 55, 40, 24, 36, 20, 43, 27, 39, 23, 2, 50, 14, 62, 1, 49, 13, 61, 34, 18, 46, 30, 33, 17, 45, 29, 10, 58, 6, 54, 9, 57, 5, 53, 42, 26, 38, 22, 41, 25, 37, 21 }; static const uint8_t dither_matrix_ro64x64[4 + 64*64] = { 64, 64, 16, 16, 6, 15, 2, 15, 2, 14, 1, 13, 2, 14, 5, 13, 0, 14, 0, 9, 6, 10, 7, 13, 6, 13, 3, 10, 5, 15, 4, 11, 0, 11, 6, 10, 7, 12, 7, 13, 0, 9, 6, 15, 6, 10, 0, 15, 1, 15, 0, 8, 0, 15, 6, 15, 7, 15, 7, 9, 1, 15, 3, 8, 1, 8, 0, 14, 9, 3, 10, 5, 10, 6, 10, 5, 9, 6, 9, 2, 9, 4, 13, 4, 13, 3, 8, 3, 10, 1, 13, 6, 11, 1, 12, 3, 14, 5, 15, 3, 8, 3, 8, 3, 12, 4, 11, 3, 13, 3, 8, 4, 9, 6, 12, 4, 11, 6, 11, 3, 10, 0, 12, 1, 11, 7, 12, 4, 12, 4, 11, 5, 1, 14, 0, 10, 2, 9, 2, 11, 1, 8, 1, 8, 3, 9, 4, 15, 7, 13, 7, 14, 7, 14, 0, 10, 0, 14, 7, 9, 0, 11, 1, 15, 0, 11, 0, 11, 3, 15, 7, 14, 6, 10, 5, 8, 0, 11, 0, 8, 7, 11, 0, 15, 0, 12, 1, 13, 6, 9, 0, 15, 4, 9, 1, 8, 10, 5, 15, 6, 13, 6, 14, 7, 14, 4, 12, 5, 15, 6, 10, 2, 11, 3, 10, 3, 11, 3, 13, 6, 11, 5, 14, 3, 14, 6, 8, 5, 14, 5, 14, 7, 10, 7, 11, 3, 13, 3, 13, 2, 14, 4, 15, 5, 15, 3, 8, 4, 11, 4, 9, 5, 12, 3, 8, 5, 15, 2, 13, 5, 3, 10, 2, 9, 5, 15, 4, 9, 0, 11, 7, 11, 0, 14, 5, 11, 5, 14, 7, 15, 6, 9, 0, 9, 0, 8, 4, 14, 6, 12, 0, 11, 4, 15, 5, 8, 6, 10, 6, 11, 6, 10, 3, 12, 5, 9, 6, 9, 5, 12, 6, 12, 7, 14, 0, 14, 2, 15, 5, 8, 2, 10, 3, 9, 14, 7, 14, 7, 8, 1, 12, 2, 14, 4, 15, 3, 11, 5, 12, 2, 8, 0, 10, 3, 12, 1, 13, 5, 14, 5, 11, 0, 11, 3, 13, 7, 8, 1, 12, 3, 15, 3, 14, 2, 15, 3, 11, 6, 15, 1, 12, 1, 9, 3, 9, 3, 11, 3, 11, 7, 11, 5, 12, 0, 14, 6, 13, 6, 5, 10, 6, 12, 3, 15, 3, 8, 7, 10, 0, 15, 7, 10, 5, 8, 1, 14, 5, 10, 7, 14, 2, 14, 0, 14, 1, 8, 3, 8, 5, 10, 5, 15, 0, 8, 6, 14, 0, 8, 2, 15, 4, 11, 6, 10, 0, 8, 5, 9, 4, 14, 1, 15, 3, 10, 1, 15, 0, 15, 5, 8, 7, 13, 15, 3, 8, 0, 9, 6, 12, 6, 15, 0, 11, 4, 14, 3, 15, 3, 10, 5, 12, 3, 10, 3, 9, 6, 9, 5, 13, 5, 15, 6, 14, 0, 9, 0, 12, 4, 11, 3, 15, 4, 11, 6, 15, 2, 15, 3, 14, 4, 13, 2, 11, 1, 8, 5, 12, 7, 9, 4, 8, 5, 12, 2, 11, 0, 0, 13, 5, 11, 5, 14, 5, 15, 7, 9, 5, 8, 2, 10, 3, 15, 6, 9, 3, 12, 5, 11, 6, 14, 3, 13, 6, 13, 0, 9, 1, 11, 3, 8, 3, 9, 4, 9, 6, 14, 7, 8, 6, 12, 7, 13, 6, 9, 5, 13, 3, 15, 5, 14, 3, 15, 4, 12, 4, 12, 2, 13, 0, 15, 10, 7, 14, 1, 8, 2, 10, 2, 12, 0, 13, 1, 13, 5, 11, 6, 12, 3, 9, 6, 15, 1, 9, 1, 10, 6, 10, 3, 15, 6, 15, 5, 15, 7, 12, 6, 12, 0, 9, 3, 12, 3, 11, 3, 8, 3, 13, 2, 9, 3, 9, 6, 10, 2, 11, 6, 9, 0, 9, 1, 9, 6, 10, 4, 3, 10, 4, 9, 4, 9, 6, 13, 3, 15, 0, 8, 4, 10, 0, 14, 5, 15, 7, 15, 2, 12, 7, 10, 5, 15, 7, 8, 0, 9, 0, 8, 6, 14, 4, 15, 2, 15, 1, 15, 0, 9, 5, 14, 0, 12, 7, 10, 6, 8, 6, 11, 0, 13, 7, 14, 1, 13, 2, 10, 5, 9, 0, 14, 14, 6, 13, 1, 12, 3, 8, 3, 9, 6, 12, 4, 15, 2, 8, 5, 9, 1, 10, 1, 8, 5, 15, 0, 8, 2, 12, 3, 12, 5, 12, 5, 11, 2, 10, 2, 8, 5, 10, 5, 12, 4, 9, 3, 8, 4, 13, 1, 15, 0, 12, 3, 8, 4, 10, 3, 9, 5, 13, 5, 13, 3, 11, 4, 4, 12, 3, 10, 6, 9, 0, 14, 0, 8, 1, 9, 1, 9, 6, 15, 0, 10, 2, 9, 6, 10, 7, 10, 2, 14, 4, 8, 0, 13, 4, 8, 1, 11, 6, 14, 7, 14, 0, 8, 4, 14, 7, 14, 4, 9, 2, 12, 0, 12, 3, 8, 5, 9, 6, 14, 0, 9, 2, 14, 5, 10, 1, 15, 10, 2, 15, 6, 14, 3, 11, 6, 15, 4, 15, 4, 13, 5, 11, 3, 12, 4, 14, 5, 12, 0, 13, 3, 8, 6, 15, 2, 10, 6, 13, 2, 12, 6, 8, 3, 10, 3, 14, 5, 8, 0, 10, 3, 13, 2, 9, 6, 10, 6, 15, 4, 13, 2, 8, 3, 14, 6, 11, 7, 13, 0, 10, 5, 2, 13, 1, 9, 5, 10, 3, 15, 6, 10, 0, 15, 4, 15, 4, 15, 6, 9, 0, 14, 1, 9, 6, 12, 0, 11, 5, 9, 0, 14, 5, 14, 6, 9, 3, 11, 7, 11, 1, 15, 1, 9, 3, 13, 5, 12, 0, 9, 2, 9, 6, 13, 2, 9, 6, 13, 7, 11, 7, 8, 0, 13, 3, 9, 9, 6, 13, 6, 13, 0, 8, 5, 14, 2, 11, 7, 11, 2, 11, 2, 12, 3, 11, 4, 13, 4, 10, 3, 15, 5, 13, 0, 11, 6, 10, 0, 13, 3, 15, 7, 14, 3, 8, 4, 12, 6, 9, 6, 10, 3, 14, 5, 13, 5, 11, 3, 13, 6, 8, 2, 14, 3, 15, 3, 11, 4, 12, 6, 4, 10, 7, 11, 1, 10, 2, 14, 7, 14, 0, 14, 4, 15, 0, 8, 0, 15, 5, 15, 7, 11, 7, 14, 6, 12, 0, 8, 1, 15, 1, 14, 2, 8, 0, 11, 4, 8, 5, 11, 1, 15, 0, 12, 7, 14, 0, 11, 4, 8, 2, 8, 0, 14, 2, 14, 4, 9, 4, 10, 7, 15, 5, 14, 14, 0, 13, 2, 14, 5, 11, 5, 10, 2, 10, 6, 11, 3, 12, 5, 11, 7, 11, 1, 12, 3, 11, 3, 9, 3, 12, 4, 11, 6, 9, 5, 14, 5, 12, 7, 14, 2, 12, 1, 8, 4, 11, 7, 10, 3, 14, 4, 14, 0, 14, 5, 11, 7, 11, 5, 15, 1, 14, 0, 10, 3, 10, 0, 5, 12, 7, 10, 7, 11, 0, 14, 5, 14, 1, 14, 3, 8, 0, 15, 0, 9, 5, 9, 1, 8, 6, 15, 6, 15, 7, 13, 7, 9, 4, 11, 0, 10, 6, 10, 7, 12, 7, 15, 0, 9, 7, 14, 2, 11, 0, 12, 7, 11, 7, 13, 0, 9, 6, 14, 2, 11, 1, 15, 0, 8, 0, 10, 10, 3, 15, 3, 15, 3, 8, 4, 11, 3, 11, 6, 15, 6, 11, 5, 12, 5, 13, 0, 15, 4, 8, 1, 11, 3, 10, 3, 15, 3, 14, 1, 14, 6, 13, 3, 11, 3, 11, 3, 13, 6, 8, 3, 12, 5, 8, 4, 13, 3, 8, 2, 13, 4, 8, 3, 14, 7, 8, 5, 14, 4, 15, 5, 3, 9, 0, 10, 0, 12, 3, 11, 7, 10, 6, 8, 7, 12, 0, 11, 1, 8, 5, 12, 5, 12, 7, 11, 2, 14, 0, 10, 0, 12, 0, 15, 2, 9, 5, 9, 0, 15, 4, 14, 6, 9, 7, 11, 6, 10, 0, 9, 1, 9, 0, 14, 4, 12, 5, 9, 6, 10, 4, 11, 6, 12, 5, 12, 12, 6, 15, 4, 8, 4, 15, 7, 15, 3, 14, 0, 9, 3, 13, 5, 14, 5, 11, 1, 9, 0, 15, 3, 8, 7, 12, 7, 8, 5, 9, 4, 12, 6, 12, 1, 10, 6, 9, 0, 15, 3, 14, 0, 15, 3, 13, 5, 15, 5, 9, 6, 10, 0, 14, 0, 13, 1, 15, 1, 8, 3, 8, 1, 2, 8, 7, 15, 0, 11, 1, 12, 1, 12, 0, 15, 0, 11, 2, 13, 1, 10, 5, 8, 7, 13, 6, 10, 0, 11, 4, 12, 7, 10, 1, 9, 1, 9, 0, 12, 2, 11, 7, 15, 1, 11, 0, 9, 2, 8, 4, 12, 2, 12, 6, 14, 4, 14, 3, 13, 3, 15, 1, 14, 4, 15, 6, 12, 13, 6, 11, 3, 12, 4, 8, 4, 9, 4, 9, 5, 12, 6, 10, 5, 14, 7, 14, 3, 10, 2, 13, 3, 14, 5, 8, 2, 13, 1, 15, 6, 15, 4, 8, 4, 15, 5, 10, 1, 14, 5, 12, 4, 13, 5, 11, 1, 9, 5, 9, 3, 9, 0, 10, 7, 10, 7, 11, 6, 10, 3, 9, 0, 3, 15, 5, 8, 1, 9, 5, 8, 4, 13, 6, 8, 4, 15, 6, 10, 6, 14, 7, 11, 0, 9, 1, 12, 0, 8, 6, 12, 6, 10, 3, 12, 7, 13, 7, 11, 3, 8, 0, 13, 7, 12, 0, 15, 4, 12, 5, 8, 4, 15, 5, 10, 0, 15, 3, 15, 1, 8, 1, 15, 4, 14, 6, 15, 10, 6, 14, 0, 13, 4, 13, 1, 9, 1, 13, 3, 11, 2, 12, 1, 8, 3, 12, 3, 14, 6, 8, 4, 15, 4, 8, 3, 15, 1, 9, 6, 11, 2, 14, 3, 13, 7, 10, 4, 10, 3, 8, 7, 11, 2, 12, 1, 11, 1, 14, 3, 9, 6, 9, 7, 13, 5, 11, 7, 10, 2, 9, 3, 1, 9, 0, 14, 7, 12, 7, 14, 5, 9, 6, 10, 6, 14, 6, 15, 1, 14, 7, 10, 3, 12, 0, 14, 5, 9, 6, 8, 1, 12, 0, 13, 2, 8, 4, 9, 6, 15, 0, 14, 4, 10, 7, 11, 5, 14, 2, 8, 4, 15, 3, 8, 7, 13, 1, 9, 0, 15, 0, 14, 5, 14, 0, 14, 15, 4, 10, 6, 11, 1, 10, 2, 14, 3, 15, 1, 10, 3, 10, 3, 8, 4, 12, 3, 10, 6, 9, 6, 14, 2, 12, 3, 11, 4, 9, 6, 12, 5, 15, 0, 10, 3, 8, 4, 14, 1, 14, 3, 11, 2, 12, 7, 11, 2, 12, 6, 10, 1, 13, 4, 11, 4, 10, 5, 11, 2, 11, 7, 0, 8, 1, 11, 4, 8, 4, 10, 6, 11, 3, 10, 6, 10, 6, 8, 1, 11, 1, 15, 7, 15, 6, 9, 1, 9, 7, 8, 0, 14, 5, 12, 3, 13, 4, 12, 0, 15, 5, 12, 1, 12, 7, 9, 6, 8, 3, 15, 0, 13, 6, 15, 7, 15, 7, 13, 2, 15, 1, 14, 2, 15, 0, 8, 12, 5, 13, 5, 15, 1, 12, 1, 15, 1, 14, 6, 14, 2, 14, 2, 14, 6, 11, 6, 11, 3, 12, 2, 13, 6, 15, 3, 9, 5, 8, 2, 8, 5, 9, 1, 9, 4, 9, 2, 8, 4, 13, 1, 13, 0, 11, 6, 11, 6, 9, 2, 11, 3, 10, 1, 10, 6, 9, 6, 11, 7, 15, 5, 5, 12, 0, 9, 5, 8, 5, 9, 0, 10, 2, 13, 4, 8, 5, 14, 2, 9, 2, 15, 4, 8, 2, 8, 0, 12, 6, 12, 0, 10, 5, 15, 4, 9, 3, 13, 0, 9, 4, 15, 1, 14, 1, 9, 0, 9, 2, 14, 6, 12, 0, 15, 7, 9, 2, 9, 0, 15, 6, 10, 3, 15, 3, 13, 9, 1, 15, 5, 12, 3, 14, 0, 15, 7, 8, 5, 14, 1, 11, 1, 13, 6, 10, 7, 13, 0, 14, 5, 10, 7, 11, 3, 15, 6, 11, 1, 12, 2, 8, 4, 13, 5, 10, 2, 10, 6, 13, 5, 13, 4, 10, 5, 9, 3, 8, 4, 12, 1, 12, 5, 11, 6, 14, 3, 11, 7, 11, 7, 2, 11, 4, 12, 7, 8, 3, 15, 3, 11, 4, 11, 7, 10, 1, 10, 2, 10, 0, 11, 4, 12, 0, 10, 1, 8, 7, 11, 1, 10, 1, 15, 7, 10, 2, 11, 1, 9, 6, 14, 2, 11, 1, 14, 4, 12, 2, 13, 1, 9, 5, 8, 7, 11, 7, 10, 7, 15, 4, 13, 3, 10, 1, 11, 13, 5, 8, 0, 15, 3, 11, 6, 12, 7, 15, 3, 15, 1, 15, 4, 14, 5, 13, 6, 8, 1, 15, 6, 13, 5, 12, 2, 15, 4, 9, 4, 15, 3, 14, 6, 14, 5, 9, 3, 12, 6, 11, 5, 8, 2, 9, 5, 12, 5, 15, 1, 13, 3, 14, 2, 8, 0, 8, 0, 12, 7, 15, 5, 0, 14, 1, 8, 2, 8, 2, 15, 2, 9, 6, 12, 0, 12, 7, 12, 7, 13, 0, 15, 6, 14, 5, 11, 1, 12, 5, 8, 5, 8, 1, 14, 0, 10, 4, 12, 6, 9, 6, 8, 0, 9, 0, 15, 4, 15, 5, 13, 1, 15, 5, 12, 7, 10, 6, 14, 4, 9, 6, 15, 0, 15, 7, 13, 10, 4, 13, 5, 13, 5, 11, 5, 12, 5, 9, 3, 9, 4, 8, 3, 9, 3, 10, 6, 11, 1, 15, 2, 8, 5, 15, 1, 13, 2, 11, 6, 14, 6, 8, 2, 15, 3, 14, 3, 13, 6, 9, 6, 9, 0, 10, 2, 10, 7, 11, 1, 15, 3, 9, 3, 13, 3, 10, 3, 9, 4, 10, 1, 4, 11, 7, 10, 2, 14, 4, 15, 3, 15, 2, 15, 7, 10, 2, 14, 1, 8, 0, 15, 2, 8, 1, 12, 2, 13, 1, 8, 7, 12, 6, 11, 1, 10, 4, 11, 2, 15, 0, 13, 0, 12, 7, 11, 5, 12, 7, 8, 6, 15, 4, 12, 7, 10, 6, 10, 0, 10, 0, 11, 4, 12, 7, 10, 13, 2, 14, 2, 10, 7, 11, 1, 11, 7, 11, 7, 12, 3, 11, 7, 13, 4, 11, 6, 12, 5, 11, 6, 10, 7, 13, 4, 11, 3, 15, 3, 14, 4, 15, 3, 8, 5, 10, 7, 10, 6, 15, 3, 9, 3, 15, 1, 11, 3, 9, 0, 15, 1, 15, 3, 15, 4, 13, 5, 8, 3, 15, 3, 3, 9, 1, 12, 4, 14, 0, 11, 7, 15, 1, 11, 3, 14, 0, 11, 5, 10, 2, 10, 2, 8, 6, 14, 7, 11, 0, 10, 7, 14, 7, 15, 6, 9, 6, 9, 1, 12, 7, 9, 4, 8, 7, 13, 0, 13, 6, 12, 6, 8, 1, 9, 1, 15, 4, 12, 0, 12, 4, 10, 0, 13, 0, 12, 13, 5, 10, 6, 9, 1, 13, 6, 11, 0, 14, 7, 11, 7, 14, 5, 15, 2, 13, 5, 15, 4, 9, 2, 13, 3, 14, 7, 10, 3, 10, 3, 12, 3, 12, 2, 8, 5, 15, 3, 13, 3, 11, 3, 10, 4, 9, 3, 15, 1, 14, 4, 11, 6, 10, 3, 8, 4, 13, 0, 10, 4, 8, 5, 5, 8, 3, 13, 4, 14, 1, 13, 7, 9, 2, 14, 4, 14, 4, 12, 7, 10, 6, 10, 5, 12, 2, 11, 6, 12, 7, 13, 4, 11, 2, 11, 3, 8, 5, 15, 2, 10, 1, 9, 2, 12, 0, 12, 0, 10, 1, 12, 0, 9, 7, 10, 0, 15, 4, 9, 0, 8, 1, 14, 4, 14, 4, 13, 12, 2, 8, 6, 10, 0, 8, 5, 13, 3, 8, 5, 10, 3, 11, 2, 15, 0, 13, 3, 8, 2, 15, 6, 9, 1, 8, 3, 14, 1, 15, 5, 14, 7, 8, 0, 12, 6, 15, 5, 8, 5, 9, 4, 15, 5, 8, 5, 15, 4, 12, 3, 9, 6, 12, 3, 13, 5, 11, 7, 10, 1, 8, 3, 4, 15, 2, 11, 2, 10, 6, 11, 3, 14, 7, 15, 0, 10, 0, 14, 3, 12, 4, 8, 7, 9, 3, 11, 0, 13, 2, 12, 2, 13, 5, 8, 5, 8, 2, 11, 4, 12, 2, 10, 7, 8, 6, 10, 7, 12, 7, 11, 0, 15, 7, 8, 7, 15, 0, 9, 2, 12, 6, 13, 0, 9, 4, 9, 8, 3, 12, 5, 15, 5, 13, 1, 8, 7, 8, 3, 12, 5, 9, 5, 9, 6, 14, 1, 12, 3, 15, 7, 10, 7, 8, 5, 8, 5, 15, 0, 13, 1, 14, 7, 9, 1, 14, 7, 13, 2, 12, 3, 8, 1, 13, 3, 8, 4, 12, 0, 11, 3, 12, 5, 11, 5, 9, 3, 12, 6, 14, 2, 5, 13, 2, 8, 6, 12, 7, 10, 5, 14, 4, 11, 2, 11, 2, 13, 2, 13, 5, 10, 5, 9, 0, 10, 7, 13, 4, 15, 2, 9, 1, 9, 5, 9, 6, 10, 4, 12, 5, 12, 4, 10, 3, 14, 3, 15, 1, 8, 4, 12, 5, 8, 5, 14, 7, 11, 7, 15, 5, 11, 2, 13, 1, 9, 10, 0, 13, 6, 10, 2, 15, 3, 8, 1, 12, 2, 14, 6, 8, 5, 10, 7, 13, 1, 13, 1, 12, 7, 9, 1, 9, 2, 12, 5, 12, 4, 12, 3, 12, 2, 9, 0, 8, 1, 15, 1, 10, 7, 10, 6, 12, 4, 11, 1, 14, 2, 11, 0, 14, 1, 9, 2, 12, 2, 8, 5, 13, 5, 6, 10, 7, 11, 0, 9, 3, 9, 5, 11, 0, 8, 0, 13, 2, 13, 4, 13, 5, 9, 4, 8, 2, 8, 0, 10, 0, 14, 6, 9, 6, 9, 4, 14, 7, 9, 0, 11, 7, 10, 7, 11, 7, 14, 4, 14, 5, 15, 4, 8, 4, 11, 5, 14, 0, 8, 1, 14, 7, 14, 2, 11, 7, 13, 12, 3, 13, 3, 14, 5, 15, 6, 12, 0, 15, 4, 11, 5, 9, 5, 9, 2, 14, 1, 12, 1, 13, 5, 15, 4, 11, 7, 12, 1, 13, 2, 8, 1, 12, 0, 15, 6, 14, 2, 14, 3, 10, 1, 11, 3, 11, 0, 12, 1, 15, 2, 11, 1, 12, 4, 9, 4, 10, 2, 13, 6, 10, 2, 2, 8, 4, 8, 4, 9, 0, 9, 2, 15, 5, 9, 6, 11, 7, 14, 0, 9, 2, 12, 2, 8, 0, 10, 1, 9, 7, 8, 2, 9, 1, 11, 2, 11, 2, 8, 1, 9, 1, 11, 7, 13, 6, 11, 1, 11, 0, 9, 2, 13, 4, 14, 1, 15, 2, 8, 7, 15, 0, 13, 6, 9, 4, 13, 13, 5, 13, 1, 14, 0, 12, 6, 9, 6, 12, 0, 13, 0, 11, 3, 13, 6, 11, 7, 12, 5, 14, 6, 14, 5, 15, 3, 13, 6, 14, 7, 15, 6, 15, 5, 13, 4, 14, 5, 8, 2, 14, 3, 14, 6, 14, 5, 10, 6, 10, 2, 9, 5, 15, 5, 11, 1, 8, 5, 13, 3, 11, 2, 2, 8, 6, 8, 0, 9, 1, 15, 0, 11, 4, 15, 7, 8, 4, 8, 1, 13, 7, 11, 1, 13, 5, 15, 1, 15, 2, 9, 0, 13, 5, 12, 3, 12, 2, 8, 1, 10, 1, 13, 5, 15, 3, 9, 3, 9, 2, 12, 5, 14, 6, 13, 1, 9, 6, 9, 1, 8, 4, 15, 7, 10, 7, 15, 14, 5, 14, 3, 12, 4, 10, 7, 12, 7, 8, 2, 12, 1, 13, 2, 11, 4, 13, 3, 8, 5, 8, 1, 10, 7, 12, 6, 9, 5, 8, 0, 8, 7, 14, 5, 13, 6, 8, 4, 8, 2, 12, 6, 13, 5, 8, 7, 8, 3, 10, 3, 13, 4, 12, 1, 13, 4, 11, 1, 14, 3, 8, 1, 2, 13, 6, 10, 0, 9, 1, 15, 0, 12, 4, 11, 2, 9, 0, 9, 0, 13, 1, 8, 0, 11, 5, 9, 6, 13, 2, 15, 2, 12, 7, 12, 6, 15, 2, 14, 1, 13, 1, 14, 4, 11, 2, 14, 1, 9, 0, 15, 1, 12, 5, 14, 6, 13, 2, 15, 3, 9, 1, 15, 7, 8, 1, 14, 11, 7, 13, 3, 15, 6, 8, 4, 8, 4, 14, 2, 14, 6, 13, 6, 11, 4, 14, 4, 15, 4, 14, 3, 10, 2, 10, 5, 9, 6, 9, 3, 9, 1, 11, 6, 9, 4, 8, 4, 15, 0, 8, 4, 13, 4, 9, 4, 9, 5, 9, 3, 10, 2, 9, 5, 12, 6, 8, 4, 12, 0, 11, 4, 4, 10, 4, 13, 1, 12, 7, 8, 0, 13, 4, 12, 2, 13, 1, 14, 7, 15, 3, 8, 7, 12, 3, 10, 4, 9, 4, 11, 7, 8, 2, 11, 2, 10, 0, 12, 1, 14, 6, 14, 7, 13, 7, 11, 3, 10, 0, 10, 4, 9, 3, 13, 0, 13, 7, 8, 7, 9, 7, 11, 2, 8, 1, 14, 15, 0, 11, 1, 9, 6, 15, 3, 10, 7, 9, 1, 9, 7, 10, 7, 10, 1, 12, 5, 8, 1, 15, 7, 13, 3, 12, 2, 13, 2, 14, 7, 14, 5, 10, 4, 8, 4, 11, 1, 10, 3, 14, 0, 14, 6, 14, 5, 13, 2, 8, 7, 10, 7, 13, 2, 14, 2, 13, 2, 14, 5, 11, 6, 7, 11, 5, 13, 1, 11, 7, 8, 5, 14, 4, 13, 1, 14, 5, 10, 2, 15, 4, 13, 7, 11, 7, 10, 1, 8, 5, 9, 4, 12, 7, 10, 1, 13, 7, 13, 7, 11, 0, 14, 5, 14, 2, 14, 4, 12, 4, 13, 7, 13, 7, 8, 2, 14, 2, 13, 5, 9, 5, 9, 2, 8, 1, 10, 12, 0, 8, 2, 13, 4, 12, 1, 11, 1, 10, 1, 10, 4, 12, 2, 11, 5, 8, 1, 15, 3, 14, 1, 13, 4, 14, 2, 9, 2, 12, 2, 8, 4, 8, 2, 12, 3, 10, 6, 11, 0, 11, 7, 10, 1, 9, 2, 10, 0, 13, 1, 11, 7, 10, 6, 13, 1, 12, 1, 12, 6, 14, 7, 2, 14, 4, 15, 7, 14, 5, 8, 4, 13, 2, 10, 4, 13, 0, 13, 1, 11, 1, 12, 7, 12, 6, 13, 4, 13, 4, 13, 2, 12, 2, 13, 1, 13, 7, 10, 2, 11, 2, 10, 7, 15, 7, 11, 5, 13, 2, 8, 2, 14, 4, 9, 2, 14, 0, 8, 1, 8, 7, 10, 5, 15, 6, 14, 11, 7, 10, 2, 11, 1, 13, 0, 11, 1, 15, 7, 8, 1, 9, 4, 13, 4, 8, 4, 8, 3, 10, 0, 9, 0, 9, 2, 9, 7, 9, 6, 10, 7, 13, 1, 13, 7, 14, 6, 11, 3, 12, 2, 8, 2, 12, 5, 11, 5, 12, 2, 10, 5, 14, 4, 12, 4, 13, 2, 9, 1, 10, 0, 2, 12, 0, 13, 7, 13, 2, 12, 4, 8, 1, 12, 4, 13, 6, 11, 7, 13, 7, 13, 1, 9, 7, 10, 2, 10, 1, 9, 2, 10, 1, 11, 6, 9, 4, 13, 2, 10, 0, 10, 2, 14, 0, 13, 7, 10, 7, 10, 0, 12, 0, 9, 0, 13, 2, 13, 1, 9, 0, 15, 2, 14, 2, 13, 11, 7, 8, 4, 10, 2, 10, 7, 12, 1, 11, 7, 8, 2, 13, 1, 10, 0, 10, 2, 13, 6, 14, 0, 14, 7, 13, 6, 14, 5, 13, 4, 15, 1, 10, 2, 13, 7, 13, 7, 9, 4, 9, 6, 13, 3, 13, 3, 8, 4, 13, 4, 10, 6, 10, 5, 12, 4, 10, 7, 11, 6, 9, 6, 4, 14, 6, 11, 7, 13, 6, 10, 4, 8, 4, 11, 6, 8, 5, 13, 7, 14, 7, 14, 1, 9, 0, 12, 1, 9, 1, 12, 4, 14, 7, 10, 4, 13, 7, 13, 7, 11, 4, 10, 1, 11, 7, 13, 4, 12, 1, 10, 4, 12, 2, 8, 2, 12, 2, 10, 2, 12, 1, 12, 4, 12, 2, 12, 8, 2, 13, 2, 8, 1, 13, 1, 12, 1, 12, 2, 12, 2, 11, 1, 9, 2, 11, 3, 12, 4, 8, 4, 12, 4, 10, 6, 10, 1, 13, 2, 11, 2, 10, 1, 12, 0, 14, 2, 14, 4, 9, 2, 8, 1, 13, 4, 8, 0, 12, 4, 11, 6, 12, 6, 11, 7, 10, 7, 11, 2, 10, 7 }; /*static const uint8_t dither_matrix_halftone8[4 + 8*8] = { 8, 8, 64, 3, 60, 53, 42, 26, 27, 43, 54, 61, 52, 41, 25, 13, 14, 28, 44, 55, 40, 24, 12, 5, 6, 15, 29, 45, 39, 23, 4, 0, 1, 7, 16, 30, 38, 22, 11, 3, 2, 8, 17, 31, 51, 37, 21, 10, 9, 18, 32, 46, 59, 50, 36, 20, 19, 33, 47, 56, 63, 58, 49, 35, 34, 48, 57, 62 }; */ static const uint8_t dither_matrix_diagonal45_8[4 + 8*8] = { 8, 8, 64, 2, 16, 32, 48, 56, 40, 24, 8, 0, 36, 52, 60, 44, 28, 12, 4, 20, 49, 57, 41, 25, 9, 1, 17, 33, 61, 45, 29, 13, 5, 21, 37, 53, 42, 26, 10, 2, 18, 34, 50, 58, 30, 14, 6, 22, 38, 54, 62, 46, 11, 3, 19, 35, 51, 59, 43, 27, 7, 23, 39, 55, 63, 47, 31, 15 }; typedef struct halftone_pixelinfo { int x; int y; double distance; double angle; } halftone_pixelinfo; static inline halftone_pixelinfo* halftone_pixel_make(int w, int h) { int x, y, k; halftone_pixelinfo* hp = Gif_NewArray(halftone_pixelinfo, w * h); for (y = k = 0; y != h; ++y) for (x = 0; x != w; ++x, ++k) { hp[k].x = x; hp[k].y = y; hp[k].distance = -1; } return hp; } static inline void halftone_pixel_combine(halftone_pixelinfo* hp, double cx, double cy) { double d = (hp->x - cx) * (hp->x - cx) + (hp->y - cy) * (hp->y - cy); if (hp->distance < 0 || d < hp->distance) { hp->distance = d; hp->angle = atan2(hp->y - cy, hp->x - cx); } } static inline int halftone_pixel_compare(const void* va, const void* vb) { const halftone_pixelinfo* a = (const halftone_pixelinfo*) va; const halftone_pixelinfo* b = (const halftone_pixelinfo*) vb; if (fabs(a->distance - b->distance) > 0.01) return a->distance < b->distance ? -1 : 1; else return a->angle < b->angle ? -1 : 1; } static uint8_t* halftone_pixel_matrix(halftone_pixelinfo* hp, int w, int h, int nc) { int i; uint8_t* m = Gif_NewArray(uint8_t, 4 + w * h); m[0] = w; m[1] = h; m[3] = nc; if (w * h > 255) { double s = 255. / (w * h); m[2] = 255; for (i = 0; i != w * h; ++i) m[4 + hp[i].x + hp[i].y*w] = (int) (i * s); } else { m[2] = w * h; for (i = 0; i != w * h; ++i) m[4 + hp[i].x + hp[i].y*w] = i; } Gif_DeleteArray(hp); return m; } static uint8_t* make_halftone_matrix_square(int w, int h, int nc) { halftone_pixelinfo* hp = halftone_pixel_make(w, h); int i; for (i = 0; i != w * h; ++i) halftone_pixel_combine(&hp[i], (w-1)/2.0, (h-1)/2.0); qsort(hp, w * h, sizeof(*hp), halftone_pixel_compare); return halftone_pixel_matrix(hp, w, h, nc); } static uint8_t* make_halftone_matrix_triangular(int w, int h, int nc) { halftone_pixelinfo* hp = halftone_pixel_make(w, h); int i; for (i = 0; i != w * h; ++i) { halftone_pixel_combine(&hp[i], (w-1)/2.0, (h-1)/2.0); halftone_pixel_combine(&hp[i], -0.5, -0.5); halftone_pixel_combine(&hp[i], w-0.5, -0.5); halftone_pixel_combine(&hp[i], -0.5, h-0.5); halftone_pixel_combine(&hp[i], w-0.5, h-0.5); } qsort(hp, w * h, sizeof(*hp), halftone_pixel_compare); return halftone_pixel_matrix(hp, w, h, nc); } int set_dither_type(Gt_OutputData* od, const char* name) { int parm[4], nparm = 0; const char* comma = strchr(name, ','); char buf[256]; /* separate arguments from dither name */ if (comma && (size_t) (comma - name) < sizeof(buf)) { memcpy(buf, name, comma - name); buf[comma - name] = 0; name = buf; } for (nparm = 0; comma && *comma && isdigit((unsigned char) comma[1]); ++nparm) parm[nparm] = strtol(&comma[1], (char**) &comma, 10); /* parse dither name */ if (od->dither_type == dither_ordered_new) Gif_DeleteArray(od->dither_data); od->dither_type = dither_none; if (strcmp(name, "none") == 0 || strcmp(name, "posterize") == 0) /* ok */; else if (strcmp(name, "default") == 0) od->dither_type = dither_default; else if (strcmp(name, "floyd-steinberg") == 0 || strcmp(name, "fs") == 0) od->dither_type = dither_floyd_steinberg; else if (strcmp(name, "atkinson") == 0 || strcmp(name, "at") == 0) od->dither_type = dither_atkinson; else if (strcmp(name, "o3") == 0 || strcmp(name, "o3x3") == 0 || (strcmp(name, "o") == 0 && nparm >= 1 && parm[0] == 3)) { od->dither_type = dither_ordered; od->dither_data = dither_matrix_o3x3; } else if (strcmp(name, "o4") == 0 || strcmp(name, "o4x4") == 0 || (strcmp(name, "o") == 0 && nparm >= 1 && parm[0] == 4)) { od->dither_type = dither_ordered; od->dither_data = dither_matrix_o4x4; } else if (strcmp(name, "o8") == 0 || strcmp(name, "o8x8") == 0 || (strcmp(name, "o") == 0 && nparm >= 1 && parm[0] == 8)) { od->dither_type = dither_ordered; od->dither_data = dither_matrix_o8x8; } else if (strcmp(name, "ro64") == 0 || strcmp(name, "ro64x64") == 0 || strcmp(name, "o") == 0 || strcmp(name, "ordered") == 0) { od->dither_type = dither_ordered; od->dither_data = dither_matrix_ro64x64; } else if (strcmp(name, "diag45") == 0 || strcmp(name, "diagonal") == 0) { od->dither_type = dither_ordered; od->dither_data = dither_matrix_diagonal45_8; } else if (strcmp(name, "halftone") == 0 || strcmp(name, "half") == 0 || strcmp(name, "trihalftone") == 0 || strcmp(name, "trihalf") == 0) { int size = nparm >= 1 && parm[0] > 0 ? parm[0] : 6; int ncol = nparm >= 2 && parm[1] > 1 ? parm[1] : 2; od->dither_type = dither_ordered_new; od->dither_data = make_halftone_matrix_triangular(size, (int) (size * sqrt(3) + 0.5), ncol); } else if (strcmp(name, "sqhalftone") == 0 || strcmp(name, "sqhalf") == 0 || strcmp(name, "squarehalftone") == 0) { int size = nparm >= 1 && parm[0] > 0 ? parm[0] : 6; int ncol = nparm >= 2 && parm[1] > 1 ? parm[1] : 2; od->dither_type = dither_ordered_new; od->dither_data = make_halftone_matrix_square(size, size, ncol); } else return -1; if (od->dither_type == dither_ordered && nparm >= 2 && parm[1] > 1 && parm[1] != od->dither_data[3]) { int size = od->dither_data[0] * od->dither_data[1]; uint8_t* dd = Gif_NewArray(uint8_t, 4 + size); memcpy(dd, od->dither_data, 4 + size); dd[3] = parm[1]; od->dither_data = dd; od->dither_type = dither_ordered_new; } return 0; } gifsicle-1.96/src/strerror.c000066400000000000000000000007131475770763400161000ustar00rootroot00000000000000/* Some operating systems don't have strerror. This file provides a definition which David Mazieres assures me works. */ #if HAVE_CONFIG_H # include #endif #ifdef __cplusplus extern "C" { #endif char * strerror(int errno) { extern int sys_nerr; extern char *sys_errlist[]; if (errno < 0 || errno >= sys_nerr) return (char *)"bad error number"; else return sys_errlist[errno]; } #ifdef __cplusplus } #endif gifsicle-1.96/src/support.c000066400000000000000000001465601475770763400157450ustar00rootroot00000000000000/* support.c - Support functions for gifsicle. Copyright (C) 1997-2025 Eddie Kohler, ekohler@gmail.com This file is part of gifsicle. Gifsicle is free software. It is distributed under the GNU Public License, version 2; you can copy, distribute, or alter it at will, as long as this notice is kept intact and this source code is made available. There is no warranty, express or implied. */ #include #include "gifsicle.h" #include #include #include #include #include #include const char *program_name = "gifsicle"; static int verbose_pos = 0; int error_count = 0; int no_warnings = 0; static void verror(const char* landmark, int need_file, int seriousness, const char *fmt, va_list val) { char pbuf[256], buf[BUFSIZ], xbuf[BUFSIZ]; const char* xfmt; int n, i, p; size_t xi; if (!fmt || !*fmt) return; if (!landmark && need_file && active_output_data.active_output_name && mode != BLANK_MODE && mode != MERGING && nested_mode != MERGING) landmark = active_output_data.active_output_name; else if (!landmark) landmark = ""; if (seriousness > 2) xfmt = "%s:%s%s fatal error: "; else if (seriousness == 1) xfmt = "%s:%s%s warning: "; else xfmt = "%s:%s%s "; snprintf(pbuf, sizeof(pbuf), xfmt, program_name, landmark, *landmark ? ":" : ""); p = strlen(pbuf); Clp_vsnprintf(clp, buf, sizeof(buf), fmt, val); n = strlen(buf); if ((size_t) n + 1 < sizeof(buf) && (n == 0 || buf[n - 1] != '\n')) { buf[n++] = '\n'; buf[n] = 0; } xi = 0; for (i = 0; i != n; ) { /* char* pos = (char*) memchr(&buf[i], '\n', n - i); int l = (pos ? &pos[1] - &buf[i] : n - i); */ int l = n - i; int xd = snprintf(&xbuf[xi], sizeof(xbuf) - xi, "%.*s%.*s", (int) p, pbuf, (int) l, &buf[i]); i += l; xi = (xi + xd > sizeof(xbuf) ? sizeof(xbuf) : xi + xd); } if (seriousness == 1 && no_warnings) return; else if (seriousness > 1) error_count++; verbose_endline(); fwrite(xbuf, 1, xi, stderr); } void fatal_error(const char* format, ...) { va_list val; va_start(val, format); verror((const char*) 0, 0, 3, format, val); va_end(val); exit(EXIT_USER_ERR); } void lerror(const char* landmark, const char* format, ...) { va_list val; va_start(val, format); verror(landmark, 2, 2, format, val); va_end(val); } void error(int need_file, const char* format, ...) { va_list val; va_start(val, format); verror((const char*) 0, need_file, 2, format, val); va_end(val); } void lwarning(const char* landmark, const char* format, ...) { va_list val; va_start(val, format); verror(landmark, 2, 1, format, val); va_end(val); } void warning(int need_file, const char* format, ...) { va_list val; va_start(val, format); verror((const char*) 0, need_file, 1, format, val); va_end(val); } void clp_error_handler(Clp_Parser *clp, const char *message) { (void) clp; verbose_endline(); fputs(message, stderr); } void short_usage(Clp_Parser* clp) { fprintf(stderr, "Usage: %s [OPTION | FILE | FRAME]...\n\ Try %s%s --help%s for more information.\n", program_name, Clp_QuoteChar(clp, 0), program_name, Clp_QuoteChar(clp, 1)); } void usage(Clp_Parser* clp) { const char* q0 = Clp_QuoteChar(clp, 0); const char* q1 = Clp_QuoteChar(clp, 1); printf("\ Gifsicle manipulates GIF images. Its most common uses include combining\n\ single images into animations, adding transparency, optimizing animations for\n\ space, and printing information about GIFs.\n\ \n\ Usage: %s [OPTION | FILE | FRAME]...\n\n", program_name); printf("\ Mode options (at most one, before any filenames):\n\ -m, --merge Merge mode: combine inputs, write stdout\n\ -b, --batch Batch mode: modify inputs, write back to\n\ same filenames\n\ -e, --explode Explode mode: write N files for each input,\n\ one per frame, to %sinput.frame-number%s\n\ -E, --explode-by-name Explode mode, but write %sinput.name%s\n\n", q0, q1, q0, q1); printf("\ General options (also --no-OPTION for info and verbose):\n\ -I, --info Print info about input GIFs\n\ -I -I Print info to standard error; do not suppress\n\ normal output\n\ --color-info, --cinfo --info plus colormap details\n\ --extension-info, --xinfo --info plus extension details\n\ --size-info, --sinfo --info plus compression information\n\ -V, --verbose Print progress (files read and written)\n"); printf("\ -h, --help Print this message and exit\n\ --version Print version number and exit\n\ -o, --output FILE Write output to FILE\n\ -w, --no-warnings Don%st report warnings\n\ --no-ignore-errors Quit on very erroneous input GIFs\n\ --conserve-memory Conserve memory at the expense of speed\n\ --nextfile, --multifile Support concatenated GIF files\n\n", q1); printf("\ Frame selections: #num, #num1-num2, #num1-, #name\n\ \n\ Frame change options:\n\ --delete FRAMES Delete FRAMES from input\n\ --insert-before FRAME GIFS Insert GIFS before FRAMES in input\n\ --append GIFS Append GIFS to input\n\ --replace FRAMES GIFS Replace FRAMES with GIFS in input\n\ --done Done with frame changes\n\n"); printf("\ Image options (also --no-OPTION and --same-OPTION):\n\ -B, --background COL Make COL the background color\n\ --crop X,Y+WxH, --crop X,Y-X2,Y2\n\ Crop the image\n\ --crop-transparency Crop transparent borders off the image\n\ --flip-horizontal, --flip-vertical\n\ Flip the image\n"); printf("\ -i, --interlace Turn on interlacing\n\ -S, --logical-screen WxH Set logical screen to WxH\n\ -p, --position X,Y Set frame position to (X,Y)\n\ --rotate-90, --rotate-180, --rotate-270, --no-rotate\n\ Rotate the image\n\ -t, --transparent COL Make COL transparent\n\n"); printf("\ Extension options:\n\ --app-extension N D Add an app extension named N with data D\n\ -c, --comment TEXT Add a comment before the next frame\n\ --extension N D Add an extension number N with data D\n\ -n, --name TEXT Set next frame%ss name\n\ --no-comments, --no-names, --no-extensions\n\ Remove comments (names, extensions) from input\n", q1); printf("\ Animation options (also --no-OPTION and --same-OPTION):\n\ -d, --delay TIME Set frame delay to TIME (in 1/100sec)\n\ -D, --disposal METHOD Set frame disposal to METHOD\n\ -l, --loopcount[=N] Set loop extension to N (default forever)\n\ -O, --optimize[=LEVEL] Optimize output GIFs\n\ -U, --unoptimize Unoptimize input GIFs\n"); #if ENABLE_THREADS printf("\ -j, --threads[=THREADS] Use multiple threads to improve speed\n"); #endif printf("\n\ Whole-GIF options (also --no-OPTION):\n\ --careful Write larger GIFs that avoid bugs in other\n\ programs\n\ --change-color COL1 COL2 Change COL1 to COL2 throughout\n\ -k, --colors N Reduce the number of colors to N\n\ --color-method METHOD Set method for choosing reduced colors\n\ -f, --dither Dither image after changing colormap\n"); #if HAVE_POW printf("\ --gamma G Set gamma for color reduction [2.2]\n"); #endif printf("\ --lossy[=LOSSINESS] Alter image colors to shrink output file size\n\ at the cost of artifacts and noise\n\ --resize WxH Resize the output GIF to WxH\n\ --scale XFACTOR[xYFACTOR] Scale the output GIF by XFACTORxYFACTOR\n\ --resize-width W Resize to width W and proportional height\n\ --resize-height H Resize to height H and proportional width\n\ --resize-fit WxH Shrink to fit WxH, preserving aspect ratio\n\ --resize-touch WxH Resize to touch WxH, preserving aspect ratio\n\ --resize-geometry GEOM Resize to a geometry specification\n"); printf("\ --resize-method METHOD Set resizing method\n\ --resize-colors N Resize can add new colors up to N\n\ --transform-colormap CMD Transform each output colormap by shell CMD\n\ --use-colormap CMAP Output GIF uses colors from CMAP, which can\n\ be %sweb%s, %sgray%s, %sbw%s, or a GIF file\n\ --use-exact-colormap CMAP Same, but use CMAP as is (not a subset)\n\ --gray Same as --use-colormap gray\n\n", q1, q0, q1, q0, q1, q0); printf("\ Report bugs to .\n\ Too much information? Try %s%s --help | less%s.\n", q0, program_name, q1); #ifdef GIF_UNGIF printf("\ This version of Gifsicle writes uncompressed GIFs, which can be far larger\n\ than compressed GIFs. See http://www.lcdf.org/gifsicle for more information.\n"); #endif } void verbose_open(char open, const char *name) { int l = strlen(name); if (verbose_pos && verbose_pos + 3 + l > 79) { fputc('\n', stderr); verbose_pos = 0; } if (verbose_pos) { fputc(' ', stderr); verbose_pos++; } fputc(open, stderr); fputs(name, stderr); verbose_pos += 1 + l; } void verbose_close(char close) { fputc(close, stderr); verbose_pos++; } void verbose_endline(void) { if (verbose_pos) { fputc('\n', stderr); fflush(stderr); verbose_pos = 0; } } /***** * Info functions **/ const char* debug_color_str(const Gif_Color* gfc) { static int whichbuf = 0; static char buf[4][8]; whichbuf = (whichbuf + 1) % 4; snprintf(buf[whichbuf], sizeof(buf[whichbuf]), "#%02X%02X%02X", gfc->gfc_red, gfc->gfc_green, gfc->gfc_blue); return buf[whichbuf]; } static void safe_puts(const char *s, uint32_t len, FILE *f) { const char *last_safe = s; for (; len > 0; len--, s++) if (*s < ' ' || *s >= 0x7F || *s == '\\') { if (last_safe != s) { size_t n = s - last_safe; if (fwrite(last_safe, 1, n, f) != n) return; } last_safe = s + 1; switch (*s) { case '\a': fputs("\\a", f); break; case '\b': fputs("\\b", f); break; case '\f': fputs("\\f", f); break; case '\n': fputs("\\n", f); break; case '\r': fputs("\\r", f); break; case '\t': fputs("\\t", f); break; case '\v': fputs("\\v", f); break; case '\\': fputs("\\\\", f); break; case 0: if (len > 1) fputs("\\000", f); break; default: fprintf(f, "\\%03o", (unsigned char) *s); break; } } if (last_safe != s) { size_t n = s - last_safe; if (fwrite(last_safe, 1, n, f) != n) return; } } static void comment_info(FILE *where, Gif_Comment *gfcom, const char *prefix) { int i; for (i = 0; i < gfcom->count; i++) { fputs(prefix, where); safe_puts(gfcom->str[i], gfcom->len[i], where); fputc('\n', where); } } #define COLORMAP_COLS 4 static void colormap_info(FILE *where, Gif_Colormap *gfcm, const char *prefix) { int i, j; int nrows = ((gfcm->ncol - 1) / COLORMAP_COLS) + 1; for (j = 0; j < nrows; j++) { int which = j; fputs(prefix, where); for (i = 0; i < COLORMAP_COLS && which < gfcm->ncol; i++, which += nrows) { if (i) fputs(" ", where); fprintf(where, " %3d: #%02X%02X%02X", which, gfcm->col[which].gfc_red, gfcm->col[which].gfc_green, gfcm->col[which].gfc_blue); } fputc('\n', where); } } static void extension_info(FILE *where, Gif_Stream *gfs, Gif_Extension *gfex, int count, int image_position) { uint8_t *data = gfex->data; uint32_t pos = 0; uint32_t len = gfex->length; fprintf(where, " extension %d: ", count); if (gfex->kind == 255) { fprintf(where, "app '"); safe_puts(gfex->appname, gfex->applength, where); fprintf(where, "'"); } else { if (gfex->kind >= 32 && gfex->kind < 127) fprintf(where, "'%c' (0x%02X)", gfex->kind, gfex->kind); else fprintf(where, "0x%02X", gfex->kind); } if (image_position >= gfs->nimages) fprintf(where, " at end"); else fprintf(where, " before #%d", image_position); if (gfex->packetized) fprintf(where, " packetized"); fprintf(where, "\n"); /* Now, hexl the data. */ while (len > 0) { uint32_t row = 16; uint32_t i; if (row > len) row = len; fprintf(where, " %08x: ", pos); for (i = 0; i < row; i += 2) { if (i + 1 >= row) fprintf(where, "%02x ", data[i]); else fprintf(where, "%02x%02x ", data[i], data[i+1]); } for (; i < 16; i += 2) fputs(" ", where); putc(' ', where); for (i = 0; i < row; i++, data++) putc((*data >= ' ' && *data < 127 ? *data : '.'), where); putc('\n', where); pos += row; len -= row; } } void stream_info(FILE *where, Gif_Stream *gfs, const char *filename, int flags) { Gif_Extension *gfex; int n, i; if (!gfs) return; verbose_endline(); fprintf(where, "* %s %d image%s\n", (filename ? filename : ""), gfs->nimages, gfs->nimages == 1 ? "" : "s"); fprintf(where, " logical screen %dx%d\n", gfs->screen_width, gfs->screen_height); if (gfs->global) { fprintf(where, " global color table [%d]\n", gfs->global->ncol); if (flags & INFO_COLORMAPS) colormap_info(where, gfs->global, " |"); fprintf(where, " background %d\n", gfs->background); } if (gfs->end_comment) comment_info(where, gfs->end_comment, " end comment "); if (gfs->loopcount == 0) fprintf(where, " loop forever\n"); else if (gfs->loopcount > 0) fprintf(where, " loop count %u\n", (unsigned)gfs->loopcount); n = 0; for (i = 0; i < gfs->nimages; ++i) for (gfex = gfs->images[i]->extension_list; gfex; gfex = gfex->next, ++n) if (flags & INFO_EXTENSIONS) extension_info(where, gfs, gfex, n, i); for (gfex = gfs->end_extension_list; gfex; gfex = gfex->next, ++n) if (flags & INFO_EXTENSIONS) extension_info(where, gfs, gfex, n, gfs->nimages); if (n && !(flags & INFO_EXTENSIONS)) fprintf(where, " extensions %d\n", n); } static const char *disposal_names[] = { "none", "asis", "background", "previous", "4", "5", "6", "7" }; void image_info(FILE *where, Gif_Stream *gfs, Gif_Image *gfi, int flags) { int num; if (!gfs || !gfi) return; num = Gif_ImageNumber(gfs, gfi); verbose_endline(); fprintf(where, " + image #%d ", num); if (gfi->identifier) fprintf(where, "#%s ", gfi->identifier); fprintf(where, "%dx%d", gfi->width, gfi->height); if (gfi->left || gfi->top) fprintf(where, " at %d,%d", gfi->left, gfi->top); if (gfi->interlace) fprintf(where, " interlaced"); if (gfi->transparent >= 0) fprintf(where, " transparent %d", gfi->transparent); fprintf(where, "\n"); if ((flags & INFO_SIZES) && gfi->compressed) fprintf(where, " compressed size %u\n", gfi->compressed_len); if (gfi->comment) comment_info(where, gfi->comment, " comment "); if (gfi->local) { fprintf(where, " local color table [%d]\n", gfi->local->ncol); if (flags & INFO_COLORMAPS) colormap_info(where, gfi->local, " |"); } if (gfi->disposal || gfi->delay) { fprintf(where, " "); if (gfi->disposal) fprintf(where, " disposal %s", disposal_names[gfi->disposal]); if (gfi->delay) fprintf(where, " delay %d.%02ds", gfi->delay / 100, gfi->delay % 100); fprintf(where, "\n"); } } char * explode_filename(const char *filename, int number, const char *name, int max_nimages) { static char *s; int l = strlen(filename); l += name ? strlen(name) : 10; Gif_Delete(s); s = Gif_NewArray(char, l + 3); if (name) snprintf(s, l + 3, "%s.%s", filename, name); else if (max_nimages <= 1000) snprintf(s, l + 3, "%s.%03d", filename, number); else { int digits; unsigned j; unsigned max = (max_nimages < 0 ? 0 : max_nimages); for (digits = 4, j = 10000; max > j; digits++) j *= 10; snprintf(s, l + 3, "%s.%0*d", filename, digits, number); } return s; } /***** * parsing functions **/ int frame_spec_1; int frame_spec_2; char *frame_spec_name; int dimensions_x; int dimensions_y; int position_x; int position_y; Gif_Color parsed_color; Gif_Color parsed_color2; double parsed_scale_factor_x; double parsed_scale_factor_y; int parse_frame_spec(Clp_Parser *clp, const char *arg, int complain, void *thunk) { char *c; (void)thunk; frame_spec_1 = 0; frame_spec_2 = -1; frame_spec_name = 0; if (!input && !input_name) input_stream(0); if (!input) return 0; if (arg[0] != '#') { if (complain) return Clp_OptionError(clp, "frame specifications must start with #"); else return 0; } arg++; c = (char *)arg; /* Get a number range (#x, #x-y, or #x-). First, read x. */ if (isdigit(c[0])) frame_spec_1 = frame_spec_2 = strtol(c, &c, 10); else if (c[0] == '-' && isdigit(c[1])) frame_spec_1 = frame_spec_2 = Gif_ImageCount(input) + strtol(c, &c, 10); /* Then, if the next character is a dash, read y. Be careful to prevent #- from being interpreted as a frame range. */ if (c[0] == '-' && (frame_spec_2 >= 0 || c[1] != 0)) { c++; if (isdigit(c[0])) frame_spec_2 = strtol(c, &c, 10); else if (c[0] == '-' && isdigit(c[1])) frame_spec_2 = Gif_ImageCount(input) + strtol(c, &c, 10); else frame_spec_2 = Gif_ImageCount(input) - 1; } /* It really was a number range (and not a frame name) only if c is now at the end of the argument. */ if (c[0] != 0) { Gif_Image *gfi = Gif_GetNamedImage(input, arg); if (gfi) { frame_spec_name = (char *)arg; frame_spec_1 = frame_spec_2 = Gif_ImageNumber(input, gfi); return 1; } else if (complain < 0) /* -1 is special value meaning 'don't complain about frame NAMES, but do complain about frame numbers.' */ return -97; /* Return -97 on bad frame name. */ else if (complain) return Clp_OptionError(clp, "no frame named %<#%s%>", arg); else return 0; } else { if (frame_spec_1 >= 0 && frame_spec_1 < Gif_ImageCount(input) && frame_spec_2 >= 0 && frame_spec_2 < Gif_ImageCount(input)) return 1; else if (!complain) return 0; else return Clp_OptionError(clp, "frame %<#%s%> out of range, image has %d frames", arg, Gif_ImageCount(input)); } } int parse_dimensions(Clp_Parser *clp, const char *arg, int complain, void *thunk) { char *val; (void)thunk; if (*arg == '_' && arg[1] == 'x') { dimensions_x = 0; val = (char *)(arg + 1); } else dimensions_x = strtol(arg, &val, 10); if (*val == 'x') { if (val[1] == '_' && val[2] == 0) { dimensions_y = 0; val = val + 2; } else dimensions_y = strtol(val + 1, &val, 10); if (*val == 0) return 1; } if (complain) return Clp_OptionError(clp, "invalid dimensions %<%s%> (want WxH)", arg); else return 0; } int parse_position(Clp_Parser *clp, const char *arg, int complain, void *thunk) { char *val; (void)thunk; position_x = strtol(arg, &val, 10); if (*val == ',') { position_y = strtol(val + 1, &val, 10); if (*val == 0) return 1; } if (complain) return Clp_OptionError(clp, "invalid position %<%s%> (want 'X,Y')", arg); else return 0; } static double strtod_fraction(const char* arg, char** endptr) { char* val, *val2; double d = strtod(arg, &val); if (arg != val && *val == '/') { double denom = strtod(val + 1, &val2); if (val2 != val + 1 && denom) { d /= denom; val = val2; } } if (endptr) *endptr = val; return d; } int parse_scale_factor(Clp_Parser *clp, const char *arg, int complain, void *thunk) { char *val; (void)thunk; parsed_scale_factor_x = strtod_fraction(arg, &val); if (*val == 'x') { parsed_scale_factor_y = strtod_fraction(val + 1, &val); if (*val == 0) return 1; } else if (*val == 0) { parsed_scale_factor_y = parsed_scale_factor_x; return 1; } if (complain) return Clp_OptionError(clp, "invalid scale factor %<%s%> (want XxY)", arg); else return 0; } int parse_rectangle(Clp_Parser *clp, const char *arg, int complain, void *thunk) { const char *input_arg = arg; char *val; int x = position_x = strtol(arg, &val, 10); (void)thunk; if (*val == ',') { int y = position_y = strtol(val + 1, &val, 10); if (*val == '-' && parse_position(clp, val + 1, 0, 0)) { if (x >= 0 && y >= 0 && (position_x <= 0 || x < position_x) && (position_y <= 0 || y < position_y)) { /* 18.May.2008: Found it unintuitive that X,Y-0,0 acted like X,Y+-Xx-Y. Therefore changed it so that X,Y-0,0 acts like X,Y+0,0, and similar for negative dimensions. Probably safe to change this behavior since only X,Y+0,0 was documented. */ dimensions_x = (position_x <= 0 ? -position_x : position_x - x); dimensions_y = (position_y <= 0 ? -position_y : position_y - y); position_x = x; position_y = y; return 1; } } else if (*val == '+' && parse_dimensions(clp, val + 1, 0, 0)) return 1; } else if (*val == 'x') { dimensions_x = position_x; dimensions_y = strtol(val + 1, &val, 10); if (*val == 0) { position_x = position_y = 0; return 1; } } if (complain) return Clp_OptionError(clp, "invalid rectangle %<%s%> (want X1,Y1-X2,Y2 or X1,Y1+WxH", input_arg); else return 0; } static int xvalue(char c) { switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return c - '0'; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': return c - 'A' + 10; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': return c - 'a' + 10; default: return -1; } } static int parse_hex_color_channel(const char *s, int ndigits) { int val1 = xvalue(s[0]); if (val1 < 0) return -1; if (ndigits == 1) return val1 * 16 + val1; else { int val2 = xvalue(s[1]); if (val2 < 0) return -1; return val1 * 16 + val2; } } int parse_color(Clp_Parser *clp, const char *arg, int complain, void *thunk) { const char *input_arg = arg; char *str; int red, green, blue; (void)thunk; if (*arg == '#') { int len = strlen(++arg); if (len == 0 || len % 3 != 0 || (int)strspn(arg, "0123456789ABCDEFabcdef") != len) { if (complain) Clp_OptionError(clp, "invalid color %<%s%> (want #RGB or #RRGGBB)", input_arg); return 0; } len /= 3; red = parse_hex_color_channel(&arg[ 0 * len ], len); green = parse_hex_color_channel(&arg[ 1 * len ], len); blue = parse_hex_color_channel(&arg[ 2 * len ], len); goto gotrgb; } else if (!isdigit(*arg)) goto error; red = strtol(arg, &str, 10); if (*str == 0) { if (red < 0 || red > 255) goto error; parsed_color.haspixel = 1; parsed_color.pixel = red; return 1; } else if (*str != ',' && *str != '/') goto error; if (*++str == 0) goto error; green = strtol(str, &str, 10); if (*str != ',' && *str != '/') goto error; if (*++str == 0) goto error; blue = strtol(str, &str, 10); if (*str != 0) goto error; gotrgb: if (red < 0 || green < 0 || blue < 0 || red > 255 || green > 255 || blue > 255) goto error; parsed_color.gfc_red = red; parsed_color.gfc_green = green; parsed_color.gfc_blue = blue; parsed_color.haspixel = 0; return 1; error: if (complain) return Clp_OptionError(clp, "invalid color %<%s%>", input_arg); else return 0; } int parse_two_colors(Clp_Parser *clp, const char *arg, int complain, void *thunk) { Gif_Color old_color; if (parse_color(clp, arg, complain, thunk) <= 0) return 0; old_color = parsed_color; arg = Clp_Shift(clp, 0); if (!arg && complain) return Clp_OptionError(clp, "%<%O%> takes two color arguments"); else if (!arg) return 0; if (parse_color(clp, arg, complain, thunk) <= 0) return 0; parsed_color2 = parsed_color; parsed_color = old_color; return 1; } /***** * reading a file as a colormap **/ static Gif_Colormap * read_text_colormap(FILE *f, const char *name) { char buf[BUFSIZ]; Gif_Colormap *cm = Gif_NewFullColormap(0, 256); Gif_Color *col = cm->col; int ncol = 0; unsigned red, green, blue; float fred, fgreen, fblue; while (fgets(buf, BUFSIZ, f)) { if (sscanf(buf, "%g %g %g", &fred, &fgreen, &fblue) == 3) { if (fred < 0) fred = 0; if (fgreen < 0) fgreen = 0; if (fblue < 0) fblue = 0; red = (unsigned)(fred + .5); green = (unsigned)(fgreen + .5); blue = (unsigned)(fblue + .5); goto found; } else if (buf[0] == '#' && strspn(buf + 1, "0123456789abcdefABCDEF") == 3 && (!buf[4] || isspace((unsigned char) buf[4]))) { sscanf(buf + 1, "%1x%1x%1x", &red, &green, &blue); red += red << 4; green += green << 4; blue += blue << 4; goto found; } else if (buf[0] == '#' && strspn(buf + 1, "0123456789abcdefABCDEF") == 6 && (!buf[7] || isspace((unsigned char) buf[7]))) { sscanf(buf + 1, "%2x%2x%2x", &red, &green, &blue); found: if (red > 255) red = 255; if (green > 255) green = 255; if (blue > 255) blue = 255; if (ncol >= 256) { lerror(name, "maximum 256 colors allowed in colormap"); break; } else { col[ncol].gfc_red = red; col[ncol].gfc_green = green; col[ncol].gfc_blue = blue; ncol++; } } /* handle too-long lines gracefully */ if (strchr(buf, '\n') == 0) { int c; for (c = getc(f); c != '\n' && c != EOF; c = getc(f)) ; } } if (ncol == 0) { lerror(name, "file not in colormap format"); Gif_DeleteColormap(cm); return 0; } else { cm->ncol = ncol; return cm; } } static void no_gifread_error(Gif_Stream* gfs, Gif_Image* gfi, int is_error, const char *message) { (void) gfs, (void) gfi, (void) is_error, (void) message; } Gif_Colormap * read_colormap_file(const char *name, FILE *f) { Gif_Colormap *cm = 0; int c; int my_file = 0; if (name && strcmp(name, "-") == 0) name = 0; if (!f) { my_file = 1; if (!name) f = stdin; else f = fopen(name, "rb"); if (!f) { lerror(name, "%s", name, strerror(errno)); return 0; } } name = name ? name : ""; if (verbosing) verbose_open('<', name); c = getc(f); ungetc(c, f); if (c == 'G') { Gif_Stream *gfs = Gif_FullReadFile(f, GIF_READ_COMPRESSED, 0, no_gifread_error); if (!gfs) lerror(name, "file not in GIF format"); else if (!gfs->global && (gfs->nimages == 0 || !gfs->images[0]->local)) lerror(name, "can%,t use as palette (no global color table)"); else { if (gfs->errors) lwarning(name, "there were errors reading this GIF"); cm = Gif_CopyColormap(gfs->global ? gfs->global : gfs->images[0]->local); } Gif_DeleteStream(gfs); } else cm = read_text_colormap(f, name); if (my_file) fclose(f); if (verbosing) verbose_close('>'); return cm; } /***** * Frame stuff **/ Gt_Frameset * new_frameset(int initial_cap) { Gt_Frameset *fs = Gif_New(Gt_Frameset); if (initial_cap < 0) initial_cap = 0; fs->cap = initial_cap; fs->count = 0; fs->f = Gif_NewArray(Gt_Frame, initial_cap); return fs; } void clear_def_frame_once_options(void) { /* Get rid of next-frame-only options. This causes problems with frame selection. In the command 'gifsicle -nblah f.gif', the name should be applied to frame 0 of f.gif. This will happen automatically when f.gif is read, since all of its frames will be added when it is input. After frame 0, the name in def_frame will be cleared. Now, 'gifsicle -nblah f.gif #1' should apply the name to frame 1 of f.gif. But once f.gif is input, its frames are added, and the name component of def_frame is cleared!! So when #1 comes around it's gone! We handle this in gifsicle.c using the _change fields. */ def_frame.name = 0; def_frame.comment = 0; def_frame.extensions = 0; } Gt_Frame * add_frame(Gt_Frameset *fset, Gif_Stream *gfs, Gif_Image *gfi) { int number = fset->count++; while (number >= fset->cap) { fset->cap *= 2; Gif_ReArray(fset->f, Gt_Frame, fset->cap); } /* Mark the stream and the image both */ gfs->refcount++; gfi->refcount++; fset->f[number] = def_frame; fset->f[number].stream = gfs; fset->f[number].image = gfi; clear_def_frame_once_options(); return &fset->f[number]; } static Gt_Frame **merger = 0; static int nmerger = 0; static int mergercap = 0; static void merger_add(Gt_Frame *fp) { while (nmerger >= mergercap) if (mergercap) { mergercap *= 2; Gif_ReArray(merger, Gt_Frame *, mergercap); } else { mergercap = 16; merger = Gif_NewArray(Gt_Frame *, mergercap); } merger[ nmerger++ ] = fp; } static void merger_flatten(Gt_Frameset *fset, int f1, int f2) { int i; assert(f1 >= 0 && f2 < fset->count); for (i = f1; i <= f2; i++) { Gt_Frameset *nest = FRAME(fset, i).nest; if (nest && nest->count > 0) { if (FRAME(fset, i).use < 0 && nest->count == 1) { /* use < 0 means use the frame's delay, disposal and name (if not explicitly overridden), but not the frame itself. */ if (FRAME(nest, 0).delay < 0) FRAME(nest, 0).delay = FRAME(fset, i).image->delay; if (FRAME(nest, 0).disposal < 0) FRAME(nest, 0).disposal = FRAME(fset, i).image->disposal; if (FRAME(nest, 0).name == 0 && FRAME(nest, 0).no_name == 0) FRAME(nest, 0).name = Gif_CopyString(FRAME(fset, i).image->identifier); } merger_flatten(nest, 0, nest->count - 1); } if (FRAME(fset, i).use > 0) merger_add(&FRAME(fset, i)); } } static int find_color_or_error(Gif_Color *color, Gif_Stream *gfs, Gif_Image *gfi, const char *color_context) { Gif_Colormap *gfcm = gfs->global; int index; if (gfi && gfi->local) gfcm = gfi->local; if (color->haspixel == 2) { /* have pixel value, not color */ if (!gfcm || color->pixel < (uint32_t)gfcm->ncol) return color->pixel; else { if (color_context) lwarning(gfs->landmark, "%s color out of range", color_context); return -1; } } index = gfcm ? Gif_FindColor(gfcm, color) : -1; if (index < 0 && color_context) lwarning(gfs->landmark, "%s color not in colormap", color_context); return index; } static void set_background(Gif_Stream *gfs, Gt_OutputData *output_data) { Gif_Color background; int i, j, conflict, want_transparent; Gif_Image *gfi; /* Check for user-specified background. */ /* If they specified the number, silently cooperate. */ if (output_data->background.haspixel == 2) { gfs->background = output_data->background.pixel; return; } /* Otherwise, if they specified a color, search for it. */ if (output_data->background.haspixel) { if (gfs->images[0]->transparent >= 0) { static int context = 0; if (!context) { warning(1, "irrelevant background color\n" " (The background will appear transparent because" " the first image contains transparency.)"); context = 1; } else warning(1, "irrelevant background color"); } background = output_data->background; goto search; } /* If we get here, user doesn't care about background. */ /* Search for required background colors. */ conflict = want_transparent = background.haspixel = 0; for (i = j = 0; i < nmerger; i++) { if (merger[i]->total_crop) /* frame does not correspond to an image */ continue; gfi = gfs->images[j]; if (gfi->disposal == GIF_DISPOSAL_BACKGROUND || (j == 0 && (gfi->left != 0 || gfi->top != 0 || gfi->width != gfs->screen_width || gfi->height != gfs->screen_height))) { /* transparent.haspixel set below, at merge_frame_done */ int original_bg_transparent = (merger[i]->transparent.haspixel == 2); if ((original_bg_transparent && background.haspixel) || (!original_bg_transparent && want_transparent)) conflict = 2; else if (original_bg_transparent) want_transparent = 1; else if (merger[i]->transparent.haspixel) { if (background.haspixel && !GIF_COLOREQ(&background, &merger[i]->transparent)) conflict = 1; else { background = merger[i]->transparent; background.haspixel = 1; } } } j++; } /* Report conflicts. */ if (conflict || (want_transparent && gfs->images[0]->transparent < 0)) { static int context = 0; if (!context) { warning(1, "input images have conflicting background colors\n" " (This means some animation frames may appear incorrect.)"); context = 1; } else warning(1, "input images have conflicting background colors"); } /* If no important background color, bag. */ if (!background.haspixel) { gfs->background = 0; return; } search: i = find_color_or_error(&background, gfs, 0, "background"); gfs->background = (i >= 0 ? i : 0); } static void fix_total_crop(Gif_Stream *dest, Gif_Image *srci, int merger_index) { /* Salvage any relevant information from a frame that's been completely cropped away. This ends up being comments and delay. */ Gt_Frame *fr = merger[merger_index]; Gt_Frame *next_fr = 0; Gif_Image *prev_image; assert(dest->nimages > 0); prev_image = dest->images[dest->nimages - 1]; if (merger_index < nmerger - 1) next_fr = merger[merger_index + 1]; /* Don't save identifiers since the frame that was to be identified, is gone. Save comments though. */ if (!fr->no_comments && srci->comment && next_fr) { if (!next_fr->comment) next_fr->comment = Gif_NewComment(); merge_comments(next_fr->comment, srci->comment); } if (fr->comment && next_fr) { if (!next_fr->comment) next_fr->comment = Gif_NewComment(); merge_comments(next_fr->comment, fr->comment); Gif_DeleteComment(fr->comment); fr->comment = 0; } /* Save delay by adding it to the previous frame's delay. */ if (fr->delay < 0) fr->delay = srci->delay; prev_image->delay += fr->delay; /* Mark this image as totally cropped. */ fr->total_crop = 1; } static void handle_flip_and_rotate(Gif_Image* desti, Gt_Frame* fr) { desti->left += fr->left_offset; desti->top += fr->top_offset; if (fr->flip_horizontal) flip_image(desti, fr, 0); if (fr->flip_vertical) flip_image(desti, fr, 1); if (fr->rotation == 1) rotate_image(desti, fr, 1); else if (fr->rotation == 2) { flip_image(desti, fr, 0); flip_image(desti, fr, 1); } else if (fr->rotation == 3) rotate_image(desti, fr, 3); desti->left -= fr->left_offset; desti->top -= fr->top_offset; } static void analyze_crop(int nmerger, Gt_Crop* crop, int compress_immediately) { int i, l = 0x7FFFFFFF, r = 0, t = 0x7FFFFFFF, b = 0; Gif_Stream* cropped_gfs = 0; /* find cropped stream */ for (i = 0; i < nmerger; i++) if (merger[i]->crop == crop) cropped_gfs = merger[i]->stream; /* find border of frames */ for (i = 0; i < nmerger; i++) if (merger[i]->crop == crop) { Gt_Frame *fr = merger[i]; int ll, tt, rr, bb; if (!fr->position_is_offset) { ll = fr->image->left; tt = fr->image->top; rr = fr->image->left + fr->image->width; bb = fr->image->top + fr->image->height; } else { ll = tt = 0; rr = fr->stream->screen_width; bb = fr->stream->screen_height; } if (ll < l) l = ll; if (tt < t) t = tt; if (rr > r) r = rr; if (bb > b) b = bb; } if (t > b) /* total crop */ l = r = t = b = 0; crop->x = crop->spec_x + l; crop->y = crop->spec_y + t; crop->w = crop->spec_w + (crop->spec_w <= 0 ? r - crop->x : 0); crop->h = crop->spec_h + (crop->spec_h <= 0 ? b - crop->y : 0); crop->left_offset = crop->x; crop->top_offset = crop->y; if (crop->x < 0 || crop->y < 0 || crop->w <= 0 || crop->h <= 0 || crop->x + crop->w > r || crop->y + crop->h > b) { lerror(cropped_gfs ? cropped_gfs->landmark : (const char*) 0, "cropping dimensions don%,t fit image"); crop->ready = 2; } else crop->ready = 1; /* Remove transparent edges. */ if (crop->transparent_edges && crop->ready == 1) { int have_l = crop->x, have_t = crop->y, have_r = crop->x + crop->w, have_b = crop->y + crop->h; l = t = 0x7FFFFFFF, r = b = 0; for (i = 0; i < nmerger && (l > have_l || t > have_t || r < have_r || b < have_b); ++i) { if (merger[i]->crop == crop) { Gt_Frame *fr = merger[i]; Gif_Image *srci = fr->image; int ll = constrain(have_l, srci->left, have_r), tt = constrain(have_t, srci->top, have_b), rr = constrain(have_l, srci->left + srci->width, have_r), bb = constrain(have_t, srci->top + srci->height, have_b); if (srci->transparent >= 0) { int x, y; uint8_t **img; Gif_UncompressImage(fr->stream, srci); img = srci->img; /* Move top edge down over transparency */ while (tt < bb && tt < t) { uint8_t *data = img[tt - srci->top]; for (x = 0; x < srci->width; ++x) if (data[x] != srci->transparent) goto found_top; ++tt; } found_top: /* Move bottom edge up over transparency */ while (bb > tt + 1 && bb > b) { uint8_t *data = img[bb - 1 - srci->top]; for (x = 0; x < srci->width; ++x) if (data[x] != srci->transparent) goto found_bottom; --bb; } found_bottom: if (tt < bb) { /* Move left edge right over transparency */ while (ll < rr && ll < l) { for (y = tt - srci->top; y < bb - srci->top; ++y) if (img[y][ll - srci->left] != srci->transparent) goto found_left; ++ll; } found_left: /* Move right edge left over transparency */ while (rr > ll + 1 && rr > r) { for (y = tt - srci->top; y < bb - srci->top; ++y) if (img[y][rr - 1 - srci->left] != srci->transparent) goto found_right; --rr; } } found_right: if (compress_immediately > 0 && srci->compressed) Gif_ReleaseUncompressedImage(srci); } if (tt < bb) { if (ll < l) l = ll; if (tt < t) t = tt; if (rr > r) r = rr; if (bb > b) b = bb; } } } if (t > b) { crop->w = crop->h = 0; } else { crop->x = l; crop->y = t; crop->w = r - l; crop->h = b - t; crop->left_offset = crop->x; crop->top_offset = crop->y; } } } static inline int apply_frame_transparent(Gif_Image *gfi, Gt_Frame *fr) { int old_transparent = gfi->transparent; if (fr->transparent.haspixel == 255) gfi->transparent = -1; else if (fr->transparent.haspixel) { gfi->transparent = find_color_or_error(&fr->transparent, fr->stream, gfi, "transparent"); if (gfi->transparent < 0) fr->transparent.haspixel = 0; } return old_transparent; } static void mark_used_background_color(Gt_Frame* fr) { Gif_Stream* gfs = fr->stream; Gif_Image* gfi = fr->image; if ((fr->transparent.haspixel != 0 ? fr->transparent.haspixel != 255 : gfi->transparent < 0) && ((fr->disposal >= 0 ? fr->disposal : gfi->disposal) == GIF_DISPOSAL_BACKGROUND || gfi->left != 0 || gfi->top != 0 || gfi->width != gfs->screen_width || gfi->height != gfs->screen_height) && gfs->global && gfs->background < gfs->global->ncol) gfs->global->col[gfs->background].haspixel |= 1; } Gif_Stream * merge_frame_interval(Gt_Frameset *fset, int f1, int f2, Gt_OutputData *output_data, int compress_immediately, int *huge_stream) { Gif_Stream *dest = Gif_NewStream(); Gif_Colormap *global = Gif_NewFullColormap(256, 256); int i, same_compressed_ok, all_same_compressed_ok; global->ncol = 0; dest->global = global; if (output_data->active_output_name) dest->landmark = output_data->active_output_name; /* 11/23/98 A new stream's screen size is 0x0; we'll use the max of the merged-together streams' screen sizes by default (in merge_stream()) */ if (f2 < 0) f2 = fset->count - 1; nmerger = 0; merger_flatten(fset, f1, f2); if (nmerger == 0) { error(1, "empty output GIF not written"); return 0; } /* decide whether stream is huge */ { size_t s; for (i = s = 0; i < nmerger; i++) s += (((unsigned) merger[i]->image->width * (unsigned) merger[i]->image->height) / 1024) + 1; *huge_stream = (s > 200 * 1024); /* 200 MB */ if (*huge_stream && !compress_immediately) { warning(1, "huge GIF, conserving memory (processing may take a while)"); compress_immediately = 1; } } /* merge stream-specific info and clear colormaps */ for (i = 0; i < nmerger; i++) merger[i]->stream->user_flags = 1; for (i = 0; i < nmerger; i++) { if (merger[i]->stream->user_flags) { Gif_Stream *src = merger[i]->stream; Gif_CalculateScreenSize(src, 0); /* merge_stream() unmarks the global colormap */ merge_stream(dest, src, merger[i]->no_comments); src->user_flags = 0; } if (merger[i]->image->local) unmark_colors_2(merger[i]->image->local); } /* is it ok to save the same compressed image? This is true only if we will recompress later from scratch. */ /* 13.Aug.2002 - Can't save the same compressed image if we crop, so turn off all_same_compressed_ok below ('analyze crops'). Reported by Tom Schumm . */ if (output_data->colormap_size > 0 || output_data->colormap_fixed || (output_data->optimizing & GT_OPT_MASK) || output_data->scaling > 0) all_same_compressed_ok = 1; else all_same_compressed_ok = 0; /* analyze crops */ for (i = 0; i < nmerger; i++) { if (merger[i]->crop) merger[i]->crop->ready = all_same_compressed_ok = 0; } for (i = 0; i < nmerger; i++) { if (merger[i]->crop && !merger[i]->crop->ready) analyze_crop(nmerger, merger[i]->crop, compress_immediately); } /* mark used colors */ for (i = 0; i < nmerger; ++i) { int old_transp = apply_frame_transparent(merger[i]->image, merger[i]); mark_used_colors(merger[i]->stream, merger[i]->image, merger[i]->crop, compress_immediately); merger[i]->image->transparent = old_transp; mark_used_background_color(merger[i]); } /* copy stream-wide information from output_data */ if (output_data->loopcount > -2) dest->loopcount = output_data->loopcount; dest->screen_width = dest->screen_height = 0; /** ACTUALLY MERGE FRAMES INTO THE NEW STREAM **/ for (i = 0; i < nmerger; i++) { Gt_Frame *fr = merger[i]; Gif_Image *srci; Gif_Image *desti; int old_transp; /* Make a copy of the image and crop it if we're cropping */ fr->left_offset = fr->top_offset = 0; if (fr->crop) { int preserve_total_crop; srci = Gif_CopyImage(fr->image); Gif_UncompressImage(fr->stream, srci); /* Zero-delay frames are a special case. You might think it was OK to get rid of totally-cropped delay-0 frames, but many browsers treat zero-delay frames as 100ms. So don't completely crop a zero-delay frame: leave it around. Problem reported by Calle Kabo. */ preserve_total_crop = (dest->nimages == 0 || fr->delay == 0 || (fr->delay < 0 && srci->delay == 0)); if (!crop_image(srci, fr, preserve_total_crop)) { /* We cropped the image out of existence! Be careful not to make 0x0 frames. */ fix_total_crop(dest, srci, i); goto merge_frame_done; } } else { srci = fr->image; Gif_UncompressImage(fr->stream, srci); } old_transp = apply_frame_transparent(srci, fr); /* Is it ok to use the old image's compressed version? */ /* 11.Feb.2003 -- It is NOT ok to use the old image's compressed version if you are going to flip or rotate the image. Crash reported by Dan Lasley . */ same_compressed_ok = all_same_compressed_ok; if ((fr->interlacing >= 0 && fr->interlacing != srci->interlace) || fr->flip_horizontal || fr->flip_vertical || fr->rotation) same_compressed_ok = 0; desti = merge_image(dest, fr->stream, srci, fr, same_compressed_ok); srci->transparent = old_transp; /* restore real transparent value */ /* Flipping and rotating */ if (fr->flip_horizontal || fr->flip_vertical || fr->rotation) { handle_flip_and_rotate(desti, fr); } /* Names and comments */ if (fr->name || fr->no_name) { Gif_DeleteArray(desti->identifier); desti->identifier = Gif_CopyString(fr->name); } if (fr->no_comments && desti->comment) { Gif_DeleteComment(desti->comment); desti->comment = 0; } if (fr->comment) { if (!desti->comment) desti->comment = Gif_NewComment(); merge_comments(desti->comment, fr->comment); /* delete the comment early to help with memory; set field to 0 so we don't re-free it later */ Gif_DeleteComment(fr->comment); fr->comment = 0; } if (fr->interlacing >= 0) { desti->interlace = fr->interlacing; } if (fr->left >= 0) { int left = fr->left + (fr->position_is_offset ? desti->left : 0); if (left + desti->width > 65535) { error(1, "left position %d out of range", left); return 0; } desti->left = left; } if (fr->top >= 0) { int top = fr->top + (fr->position_is_offset ? desti->top : 0); if (top + desti->height > 65535) { error(1, "top position %d out of range", top); return 0; } desti->top = top; } if (fr->delay >= 0) { desti->delay = fr->delay; } if (fr->disposal >= 0) { desti->disposal = fr->disposal; } /* logical screen */ if (output_data->screen_mode <= 0) { int w, h; if (output_data->screen_mode < 0 || fr->crop || ((fr->left >= 0 || fr->top >= 0) && !fr->position_is_offset)) { w = desti->left + desti->width; h = desti->top + desti->height; } else { if (fr->rotation == 1 || fr->rotation == 3) { w = fr->stream->screen_height; h = fr->stream->screen_width; } else { w = fr->stream->screen_width; h = fr->stream->screen_height; } if (fr->left >= 0) { w += fr->left; } if (fr->top >= 0) { h += fr->top; } } if (w > dest->screen_width) { dest->screen_width = w; } if (h > dest->screen_height) { dest->screen_height = h; } } /* compress immediately if possible to save on memory */ if (desti->img) { if (compress_immediately > 0) { Gif_FullCompressImage(dest, desti, &gif_write_info); Gif_ReleaseUncompressedImage(desti); } else if (desti->compressed) Gif_ReleaseCompressedImage(desti); } else if (compress_immediately <= 0) { Gif_UncompressImage(dest, desti); Gif_ReleaseCompressedImage(desti); } merge_frame_done: /* 6/17/02 store information about image's background */ if (fr->stream->images[0]->transparent >= 0) { fr->transparent.haspixel = 2; } else if (fr->stream->global && fr->stream->background < fr->stream->global->ncol) { fr->transparent = fr->stream->global->col[fr->stream->background]; fr->transparent.haspixel = 1; } else { fr->transparent.haspixel = 0; } /* Destroy the copied, cropped image if necessary */ if (fr->crop) { Gif_DeleteImage(srci); } /* if we can, delete the image's data right now to save memory */ srci = fr->image; assert(srci->refcount > 1); if (--srci->refcount == 1) { /* only 1 reference ==> the reference is from the input stream itself */ Gif_ReleaseUncompressedImage(srci); Gif_ReleaseCompressedImage(srci); } fr->image = 0; /* 5/26/98 Destroy the stream now to help with memory. Assumes that all frames are added with add_frame() which properly increments the stream's refcount. Set field to 0 so we don't redelete */ Gif_DeleteStream(fr->stream); fr->stream = 0; } /** END MERGE LOOP **/ if (output_data->screen_mode == 1) { dest->screen_width = output_data->screen_width; dest->screen_height = output_data->screen_height; } /* Find the background color in the colormap, or add it if we can */ set_background(dest, output_data); /* The global colormap might be empty even given images that use it, if those images are entirely transparent. Since absent global colormaps are surprising, assign a black-and-white colormap */ if (dest->global->ncol == 0) { for (i = 0; i != dest->nimages; ++i) if (!dest->images[i]->local) { GIF_SETCOLOR(&dest->global->col[0], 0, 0, 0); GIF_SETCOLOR(&dest->global->col[1], 255, 255, 255); dest->global->ncol = 2; break; } } return dest; } void blank_frameset(Gt_Frameset *fset, int f1, int f2, int delete_object) { int i; if (delete_object) f1 = 0, f2 = -1; if (f2 < 0) f2 = fset->count - 1; for (i = f1; i <= f2; i++) { /* We may have deleted stream and image earlier to save on memory; see above in merge_frame_interval(); but if we didn't, do it now. */ if (FRAME(fset, i).image && FRAME(fset, i).image->refcount > 1) FRAME(fset, i).image->refcount--; Gif_DeleteStream(FRAME(fset, i).stream); Gif_DeleteComment(FRAME(fset, i).comment); if (FRAME(fset, i).nest) blank_frameset(FRAME(fset, i).nest, 0, 0, 1); } if (delete_object) { Gif_DeleteArray(fset->f); Gif_Delete(fset); } } void clear_frameset(Gt_Frameset *fset, int f1) { blank_frameset(fset, f1, -1, 0); fset->count = f1; } gifsicle-1.96/src/ungifwrt.c000066400000000000000000000523621475770763400160720ustar00rootroot00000000000000/* -*- mode: c; c-basic-offset: 2 -*- */ /* ungifwrt.c - Functions to write unGIFs -- GIFs with run-length compression, not LZW compression. Idea due to Hutchinson Avenue Software Corporation . Copyright (C) 1997-2025 Eddie Kohler, ekohler@gmail.com This file is part of the LCDF GIF library. The LCDF GIF library is free software. It is distributed under the GNU General Public License, version 2; you can copy, distribute, or alter it at will, as long as this notice is kept intact and this source code is made available. There is no warranty, express or implied. */ #if HAVE_CONFIG_H # include #elif !defined(__cplusplus) && !defined(inline) /* Assume we don't have inline by default */ # define inline #endif #include #include #include #include #ifdef __cplusplus extern "C" { #endif #define WRITE_BUFFER_SIZE 255 struct Gif_Writer { FILE *f; uint8_t *v; uint32_t pos; uint32_t cap; Gif_CompressInfo gcinfo; int global_size; int local_size; int errors; int cleared; Gif_Code* rle_next; void (*byte_putter)(uint8_t, struct Gif_Writer *); void (*block_putter)(const uint8_t *, size_t, struct Gif_Writer *); }; #define gifputbyte(b, grr) ((*grr->byte_putter)(b, grr)) #define gifputblock(b, l, grr) ((*grr->block_putter)(b, l, grr)) static inline void gifputunsigned(uint16_t uns, Gif_Writer *grr) { gifputbyte(uns & 0xFF, grr); gifputbyte(uns >> 8, grr); } static void file_byte_putter(uint8_t b, Gif_Writer *grr) { fputc(b, grr->f); } static void file_block_putter(const uint8_t *block, size_t size, Gif_Writer *grr) { if (fwrite(block, 1, size, grr->f) != size) grr->errors = 1; } static void memory_byte_putter(uint8_t b, Gif_Writer *grr) { if (grr->pos >= grr->cap) { grr->cap = (grr->cap ? grr->cap * 2 : 1024); Gif_ReArray(grr->v, uint8_t, grr->cap); } if (grr->v) { grr->v[grr->pos] = b; grr->pos++; } } static void memory_block_putter(const uint8_t *data, size_t len, Gif_Writer *grr) { while (grr->pos + len >= grr->cap) { grr->cap = (grr->cap ? grr->cap * 2 : 1024); Gif_ReArray(grr->v, uint8_t, grr->cap); } if (grr->v) { memcpy(grr->v + grr->pos, data, len); grr->pos += len; } } static int gif_writer_init(Gif_Writer* grr, FILE* f, const Gif_CompressInfo* gcinfo) { grr->f = f; grr->v = NULL; grr->pos = grr->cap = 0; if (gcinfo) grr->gcinfo = *gcinfo; else Gif_InitCompressInfo(&grr->gcinfo); grr->errors = 0; grr->cleared = 0; /* 27.Jul.2001: Must allocate GIF_MAX_CODE + 1 because we assign to rle_next[GIF_MAX_CODE]! Thanks, Jeff Brown , for supplying the buggy files. */ grr->rle_next = Gif_NewArray(Gif_Code, GIF_MAX_CODE + 1); if (f) { grr->byte_putter = file_byte_putter; grr->block_putter = file_block_putter; } else { grr->byte_putter = memory_byte_putter; grr->block_putter = memory_block_putter; } return grr->rle_next != 0; } static void gif_writer_cleanup(Gif_Writer* grr) { Gif_DeleteArray(grr->v); Gif_DeleteArray(grr->rle_next); } static inline const uint8_t * gif_imageline(Gif_Image *gfi, unsigned pos) { unsigned y, x; if (gfi->width == 0) return NULL; y = pos / gfi->width; x = pos - y * gfi->width; if (y == (unsigned) gfi->height) return NULL; else if (!gfi->interlace) return gfi->img[y] + x; else return gfi->img[Gif_InterlaceLine(y, gfi->height)] + x; } static inline unsigned gif_line_endpos(Gif_Image *gfi, unsigned pos) { unsigned y = pos / gfi->width; return (y + 1) * gfi->width; } /* Write GIFs compressed with run-length encoding, an idea from code by Hutchinson Avenue Software Corporation found in Thomas Boutell's gd library . */ static int write_compressed_data(Gif_Image *gfi, int min_code_bits, Gif_Writer *grr) { uint8_t stack_buffer[512 - 24]; uint8_t *buf = stack_buffer; unsigned bufpos = 0; unsigned bufcap = sizeof(stack_buffer) * 8; unsigned pos; unsigned clear_bufpos, clear_pos; unsigned line_endpos; const uint8_t *imageline; unsigned run; #ifndef GIF_NO_COMPRESSION #define RUN_EWMA_SHIFT 4 #define RUN_EWMA_SCALE 19 #define RUN_INV_THRESH ((unsigned) (1 << RUN_EWMA_SCALE) / 3000) unsigned run_ewma; Gif_Code* rle_next = grr->rle_next; #endif Gif_Code next_code = 0; Gif_Code output_code; uint8_t suffix; int cur_code_bits; /* Here we go! */ gifputbyte(min_code_bits, grr); #define CLEAR_CODE ((Gif_Code) (1 << min_code_bits)) #define EOI_CODE ((Gif_Code) (CLEAR_CODE + 1)) #define CUR_BUMP_CODE (1 << cur_code_bits) grr->cleared = 0; cur_code_bits = min_code_bits + 1; /* next_code set by first runthrough of output clear_code */ GIF_DEBUG(("clear(%d) eoi(%d) bits(%d) ", CLEAR_CODE, EOI_CODE, cur_code_bits)); output_code = CLEAR_CODE; /* Because output_code is clear_code, we'll initialize next_code, et al. below. */ pos = clear_pos = clear_bufpos = 0; line_endpos = gfi->width; imageline = gif_imageline(gfi, pos); while (1) { /***** * Output 'output_code' to the memory buffer. */ if (bufpos + 32 >= bufcap) { unsigned ncap = bufcap * 2 + (24 << 3); uint8_t *nbuf = Gif_NewArray(uint8_t, ncap >> 3); if (!nbuf) goto error; memcpy(nbuf, buf, bufcap >> 3); if (buf != stack_buffer) Gif_DeleteArray(buf); buf = nbuf; bufcap = ncap; } { unsigned endpos = bufpos + cur_code_bits; do { if (bufpos & 7) buf[bufpos >> 3] |= output_code << (bufpos & 7); else if (bufpos & 0x7FF) buf[bufpos >> 3] = output_code >> (bufpos - endpos + cur_code_bits); else { buf[bufpos >> 3] = 255; endpos += 8; } bufpos += 8 - (bufpos & 7); } while (bufpos < endpos); bufpos = endpos; } /***** * Handle special codes. */ if (output_code == CLEAR_CODE) { /* Clear data and prepare gfc */ cur_code_bits = min_code_bits + 1; next_code = EOI_CODE + 1; #ifndef GIF_NO_COMPRESSION { Gif_Code c; for (c = 0; c < CLEAR_CODE; ++c) rle_next[c] = CLEAR_CODE; } run_ewma = 1 << RUN_EWMA_SCALE; #endif run = 0; clear_pos = clear_bufpos = 0; GIF_DEBUG(("clear ")); } else if (output_code == EOI_CODE) break; else { if (next_code > CUR_BUMP_CODE && cur_code_bits < GIF_MAX_CODE_BITS) /* bump up compression size */ ++cur_code_bits; #ifndef GIF_NO_COMPRESSION /* Adjust current run length average. */ run = (run << RUN_EWMA_SCALE) + (1 << (RUN_EWMA_SHIFT - 1)); if (run < run_ewma) run_ewma -= (run_ewma - run) >> RUN_EWMA_SHIFT; else run_ewma += (run - run_ewma) >> RUN_EWMA_SHIFT; #else if (cur_code_bits != min_code_bits) { /* never bump up compression size -- keep cur_code_bits small by generating clear_codes */ output_code = CLEAR_CODE; continue; } #endif /* Reset run length. */ run = 0; } /***** * Find the next code to output. */ /* If height is 0 -- no more pixels to write -- we output work_node next time around. */ if (imageline) { output_code = suffix = *imageline; while (1) { imageline++; pos++; if (pos == line_endpos) { imageline = gif_imageline(gfi, pos); line_endpos += gfi->width; } run++; #ifndef GIF_NO_COMPRESSION if (!imageline || *imageline != suffix || rle_next[output_code] == CLEAR_CODE) break; output_code = rle_next[output_code]; #else break; #endif } /* Output the current code. */ if (next_code < GIF_MAX_CODE) { #ifndef GIF_NO_COMPRESSION if (imageline && *imageline == suffix) { rle_next[output_code] = next_code; rle_next[next_code] = CLEAR_CODE; } #endif next_code++; } else next_code = GIF_MAX_CODE + 1; /* to match "> CUR_BUMP_CODE" above */ /* Check whether to clear table. */ if (next_code > 4094) { int do_clear = grr->gcinfo.flags & GIF_WRITE_EAGER_CLEAR; #ifndef GIF_NO_COMPRESSION if (!do_clear) { unsigned pixels_left = (unsigned) gfi->width * (unsigned) gfi->height - pos; if (pixels_left) { /* Always clear if run_ewma gets small relative to min_code_bits. Otherwise, clear if #images/run is smaller than an empirical threshold, meaning it will take more than 3000 or so average runs to complete the image. */ if (run_ewma < ((36U << RUN_EWMA_SCALE) / min_code_bits) || pixels_left > UINT_MAX / RUN_INV_THRESH || run_ewma < pixels_left * RUN_INV_THRESH) do_clear = 1; } } #else do_clear = 1; #endif if ((do_clear || run < 7) && !clear_pos) { clear_pos = pos - run; clear_bufpos = bufpos; } else if (!do_clear && run > 50) clear_pos = clear_bufpos = 0; if (do_clear) { GIF_DEBUG(("rewind %u pixels/%d bits ", pos - clear_pos, bufpos + cur_code_bits - clear_bufpos)); output_code = CLEAR_CODE; pos = clear_pos; imageline = gif_imageline(gfi, pos); line_endpos = gif_line_endpos(gfi, pos); bufpos = clear_bufpos; buf[bufpos >> 3] &= (1 << (bufpos & 7)) - 1; grr->cleared = 1; } } } else output_code = EOI_CODE; } /* Output memory buffer to stream. */ bufpos = (bufpos + 7) >> 3; buf[(bufpos - 1) & 0xFFFFFF00] = (bufpos - 1) & 0xFF; buf[bufpos] = 0; gifputblock(buf, bufpos + 1, grr); if (buf != stack_buffer) Gif_DeleteArray(buf); return 1; error: if (buf != stack_buffer) Gif_DeleteArray(buf); return 0; } static int calculate_min_code_bits(Gif_Image *gfi, const Gif_Writer *grr) { int colors_used = -1, min_code_bits, i; if (grr->gcinfo.flags & GIF_WRITE_CAREFUL_MIN_CODE_SIZE) { /* calculate m_c_b based on colormap */ if (grr->local_size > 0) colors_used = grr->local_size; else if (grr->global_size > 0) colors_used = grr->global_size; } else if (gfi->img) { /* calculate m_c_b from uncompressed data */ int x, y, width = gfi->width, height = gfi->height; colors_used = 0; for (y = 0; y < height && colors_used < 128; y++) { uint8_t *data = gfi->img[y]; for (x = width; x > 0; x--, data++) if (*data > colors_used) colors_used = *data; } colors_used++; } else if (gfi->compressed) { /* take m_c_b from compressed image */ colors_used = 1 << gfi->compressed[0]; } else { /* should never happen */ colors_used = 256; } min_code_bits = 2; /* min_code_bits of 1 isn't allowed */ i = 4; while (i < colors_used) { min_code_bits++; i *= 2; } return min_code_bits; } static int get_color_table_size(const Gif_Stream *gfs, Gif_Image *gfi, Gif_Writer *grr); static void save_compression_result(Gif_Image *gfi, Gif_Writer *grr, int ok) { if (!(grr->gcinfo.flags & GIF_WRITE_SHRINK) || (ok && (!gfi->compressed || gfi->compressed_len > grr->pos))) { if (gfi->compressed) (*gfi->free_compressed)((void *) gfi->compressed); if (ok) { gfi->compressed_len = grr->pos; gfi->compressed_errors = 0; gfi->compressed = grr->v; gfi->free_compressed = Gif_Free; grr->v = 0; grr->cap = 0; } else gfi->compressed = 0; } grr->pos = 0; } int Gif_FullCompressImage(Gif_Stream *gfs, Gif_Image *gfi, const Gif_CompressInfo *gcinfo) { int ok = 0; uint8_t min_code_bits; Gif_Writer grr; if (!gif_writer_init(&grr, NULL, gcinfo)) { if (!(grr.gcinfo.flags & GIF_WRITE_SHRINK)) Gif_ReleaseCompressedImage(gfi); goto done; } grr.global_size = get_color_table_size(gfs, 0, &grr); grr.local_size = get_color_table_size(gfs, gfi, &grr); min_code_bits = calculate_min_code_bits(gfi, &grr); ok = write_compressed_data(gfi, min_code_bits, &grr); save_compression_result(gfi, &grr, ok); if ((grr.gcinfo.flags & (GIF_WRITE_OPTIMIZE | GIF_WRITE_EAGER_CLEAR)) == GIF_WRITE_OPTIMIZE && grr.cleared && ok) { grr.gcinfo.flags |= GIF_WRITE_EAGER_CLEAR | GIF_WRITE_SHRINK; if (write_compressed_data(gfi, min_code_bits, &grr)) save_compression_result(gfi, &grr, 1); } done: gif_writer_cleanup(&grr); return ok; } static int get_color_table_size(const Gif_Stream *gfs, Gif_Image *gfi, Gif_Writer *grr) { Gif_Colormap *gfcm = (gfi ? gfi->local : gfs->global); int ncol, totalcol, i; if (!gfcm || gfcm->ncol <= 0) return 0; /* Make sure ncol is reasonable */ ncol = gfcm->ncol; /* Possibly bump up 'ncol' based on 'transparent' values, if careful_min_code_bits */ if (grr->gcinfo.flags & GIF_WRITE_CAREFUL_MIN_CODE_SIZE) { if (gfi && gfi->transparent >= ncol) ncol = gfi->transparent + 1; else if (!gfi) for (i = 0; i < gfs->nimages; i++) if (gfs->images[i]->transparent >= ncol) ncol = gfs->images[i]->transparent + 1; } /* Make sure the colormap is a power of two entries! */ /* GIF format doesn't allow a colormap with only 1 entry. */ if (ncol > 256) ncol = 256; for (totalcol = 2; totalcol < ncol; totalcol *= 2) /* nada */; return totalcol; } static void write_color_table(Gif_Colormap *gfcm, int totalcol, Gif_Writer *grr) { Gif_Color *c = gfcm->col; int i, ncol = gfcm->ncol; for (i = 0; i < ncol && i < totalcol; i++, c++) { gifputbyte(c->gfc_red, grr); gifputbyte(c->gfc_green, grr); gifputbyte(c->gfc_blue, grr); } /* Pad out colormap with black. */ for (; i < totalcol; i++) { gifputbyte(0, grr); gifputbyte(0, grr); gifputbyte(0, grr); } } static int write_image(Gif_Stream *gfs, Gif_Image *gfi, Gif_Writer *grr) { uint8_t min_code_bits, packed = 0; grr->local_size = get_color_table_size(gfs, gfi, grr); gifputbyte(',', grr); gifputunsigned(gfi->left, grr); gifputunsigned(gfi->top, grr); gifputunsigned(gfi->width, grr); gifputunsigned(gfi->height, grr); if (grr->local_size > 0) { int size = 2; packed |= 0x80; while (size < grr->local_size) size *= 2, packed++; } if (gfi->interlace) packed |= 0x40; gifputbyte(packed, grr); if (grr->local_size > 0) write_color_table(gfi->local, grr->local_size, grr); /* calculate min_code_bits here (because calculation may involve recompression, if GIF_WRITE_CAREFUL_MIN_CODE_SIZE is true) */ min_code_bits = calculate_min_code_bits(gfi, grr); /* use existing compressed data if it exists. This will tend to whip people's asses who uncompress an image, keep the compressed data around, but modify the uncompressed data anyway. That sucks. */ if (gfi->compressed && (!(grr->gcinfo.flags & GIF_WRITE_CAREFUL_MIN_CODE_SIZE) || gfi->compressed[0] == min_code_bits)) { uint8_t *compressed = gfi->compressed; uint32_t compressed_len = gfi->compressed_len; while (compressed_len > 0) { uint16_t amt = (compressed_len > 0x7000 ? 0x7000 : compressed_len); gifputblock(compressed, amt, grr); compressed += amt; compressed_len -= amt; } } else if (!gfi->img) { Gif_UncompressImage(gfs, gfi); write_compressed_data(gfi, min_code_bits, grr); Gif_ReleaseUncompressedImage(gfi); } else write_compressed_data(gfi, min_code_bits, grr); return 1; } static void write_logical_screen_descriptor(Gif_Stream *gfs, Gif_Writer *grr) { uint8_t packed = 0x70; /* high resolution colors */ grr->global_size = get_color_table_size(gfs, 0, grr); Gif_CalculateScreenSize(gfs, 0); gifputunsigned(gfs->screen_width, grr); gifputunsigned(gfs->screen_height, grr); if (grr->global_size > 0) { uint16_t size = 2; packed |= 0x80; while (size < grr->global_size) size *= 2, packed++; } gifputbyte(packed, grr); if (gfs->background < grr->global_size) gifputbyte(gfs->background, grr); else gifputbyte(255, grr); gifputbyte(0, grr); /* no aspect ratio information */ if (grr->global_size > 0) write_color_table(gfs->global, grr->global_size, grr); } /* extension byte table: 0x01 plain text extension 0xCE name* 0xF9 graphic control extension 0xFE comment extension 0xFF application extension */ static void write_graphic_control_extension(Gif_Image *gfi, Gif_Writer *grr) { uint8_t packed = 0; gifputbyte('!', grr); gifputbyte(0xF9, grr); gifputbyte(4, grr); if (gfi->transparent >= 0) packed |= 0x01; packed |= (gfi->disposal & 0x07) << 2; gifputbyte(packed, grr); gifputunsigned(gfi->delay, grr); gifputbyte((uint8_t)gfi->transparent, grr); gifputbyte(0, grr); } static void blast_data(const uint8_t *data, int len, Gif_Writer *grr) { while (len > 0) { int s = len > 255 ? 255 : len; gifputbyte(s, grr); gifputblock(data, s, grr); data += s; len -= s; } gifputbyte(0, grr); } static void write_name_extension(char *id, Gif_Writer *grr) { gifputbyte('!', grr); gifputbyte(0xCE, grr); blast_data((uint8_t *)id, strlen(id), grr); } static void write_comment_extensions(Gif_Comment *gfcom, Gif_Writer *grr) { int i; for (i = 0; i < gfcom->count; i++) { gifputbyte('!', grr); gifputbyte(0xFE, grr); blast_data((const uint8_t *)gfcom->str[i], gfcom->len[i], grr); } } static void write_netscape_loop_extension(uint16_t value, Gif_Writer *grr) { gifputblock((const uint8_t *)"!\xFF\x0BNETSCAPE2.0\x03\x01", 16, grr); gifputunsigned(value, grr); gifputbyte(0, grr); } static void write_generic_extension(Gif_Extension *gfex, Gif_Writer *grr) { uint32_t pos = 0; if (gfex->kind < 0) return; /* ignore our private extensions */ gifputbyte('!', grr); gifputbyte(gfex->kind, grr); if (gfex->kind == 255) { /* an application extension */ if (gfex->applength) { gifputbyte(gfex->applength, grr); gifputblock((const uint8_t*) gfex->appname, gfex->applength, grr); } } if (gfex->packetized) gifputblock(gfex->data, gfex->length, grr); else { while (pos + 255 < gfex->length) { gifputbyte(255, grr); gifputblock(gfex->data + pos, 255, grr); pos += 255; } if (pos < gfex->length) { uint32_t len = gfex->length - pos; gifputbyte(len, grr); gifputblock(gfex->data + pos, len, grr); } } gifputbyte(0, grr); } static int write_gif(Gif_Stream *gfs, Gif_Writer *grr) { Gif_Extension* gfex; int ok = 0; int i; { uint8_t isgif89a = 0; if (gfs->end_comment || gfs->end_extension_list || gfs->loopcount > -1) isgif89a = 1; for (i = 0; i < gfs->nimages && !isgif89a; i++) { Gif_Image* gfi = gfs->images[i]; if (gfi->identifier || gfi->transparent != -1 || gfi->disposal || gfi->delay || gfi->comment || gfi->extension_list) isgif89a = 1; } if (isgif89a) gifputblock((const uint8_t *)"GIF89a", 6, grr); else gifputblock((const uint8_t *)"GIF87a", 6, grr); } write_logical_screen_descriptor(gfs, grr); if (gfs->loopcount > -1) write_netscape_loop_extension(gfs->loopcount, grr); for (i = 0; i < gfs->nimages; i++) if (!Gif_IncrementalWriteImage(grr, gfs, gfs->images[i])) goto done; for (gfex = gfs->end_extension_list; gfex; gfex = gfex->next) write_generic_extension(gfex, grr); if (gfs->end_comment) write_comment_extensions(gfs->end_comment, grr); gifputbyte(';', grr); ok = 1; done: return ok; } int Gif_FullWriteFile(Gif_Stream *gfs, const Gif_CompressInfo *gcinfo, FILE *f) { Gif_Writer grr; int ok = gif_writer_init(&grr, f, gcinfo) && write_gif(gfs, &grr); gif_writer_cleanup(&grr); return ok; } Gif_Writer* Gif_IncrementalWriteFileInit(Gif_Stream* gfs, const Gif_CompressInfo* gcinfo, FILE *f) { Gif_Writer* grr = Gif_New(Gif_Writer); if (!grr || !gif_writer_init(grr, f, gcinfo)) { Gif_Delete(grr); return NULL; } gifputblock((const uint8_t *)"GIF89a", 6, grr); write_logical_screen_descriptor(gfs, grr); if (gfs->loopcount > -1) write_netscape_loop_extension(gfs->loopcount, grr); return grr; } int Gif_IncrementalWriteImage(Gif_Writer* grr, Gif_Stream* gfs, Gif_Image* gfi) { Gif_Extension *gfex; for (gfex = gfi->extension_list; gfex; gfex = gfex->next) write_generic_extension(gfex, grr); if (gfi->comment) write_comment_extensions(gfi->comment, grr); if (gfi->identifier) write_name_extension(gfi->identifier, grr); if (gfi->transparent != -1 || gfi->disposal || gfi->delay) write_graphic_control_extension(gfi, grr); return write_image(gfs, gfi, grr); } int Gif_IncrementalWriteComplete(Gif_Writer* grr, Gif_Stream* gfs) { Gif_Extension* gfex; for (gfex = gfs->end_extension_list; gfex; gfex = gfex->next) write_generic_extension(gfex, grr); if (gfs->end_comment) write_comment_extensions(gfs->end_comment, grr); gifputbyte(';', grr); gif_writer_cleanup(grr); Gif_Delete(grr); return 1; } #undef Gif_CompressImage #undef Gif_WriteFile int Gif_CompressImage(Gif_Stream *gfs, Gif_Image *gfi) { return Gif_FullCompressImage(gfs, gfi, 0); } int Gif_WriteFile(Gif_Stream *gfs, FILE *f) { return Gif_FullWriteFile(gfs, 0, f); } #ifdef __cplusplus } #endif gifsicle-1.96/src/win32cfg.h000066400000000000000000000126321475770763400156500ustar00rootroot00000000000000/* Hand-edited file based on config.h.in */ /* config.h.in. Generated from configure.ac by autoheader. */ #ifndef GIFSICLE_CONFIG_H #define GIFSICLE_CONFIG_H /* Define to 1 if multithreading support is available. */ /* #undef ENABLE_THREADS */ /* Define to the number of arguments to gettimeofday (gifview only). */ /* #undef GETTIMEOFDAY_PROTO */ /* Define if GIF LZW compression is off. */ /* #undef GIF_UNGIF */ /* Define to 1 if you have the `cbrtf' function. */ #define HAVE_CBRTF 1 /* Define to 1 if `ext_vector_type' vector types are usable. */ /* #undef HAVE_EXT_VECTOR_TYPE_VECTOR_TYPES */ /* Define to 1 if the system has the type `int64_t'. */ /* #undef HAVE_INT64_T */ /* Define to 1 if you have the header file. */ #if defined(_MSC_VER) && _MSC_VER >= 1900 # define HAVE_INTTYPES_H 1 #endif /* Define to 1 if you have the header file. */ /* #undef HAVE_MEMORY_H */ /* Define to 1 if you have the `mkstemp' function. */ /* #undef HAVE_MKSTEMP */ /* Define to 1 if you have the `pow' function. */ #define HAVE_POW 1 /* Define to 1 if SIMD types should be used. */ /* #undef HAVE_SIMD */ /* Define to 1 if you have the header file. */ #if defined(_MSC_VER) && _MSC_VER >= 1900 # define HAVE_STDINT_H 1 #endif /* Define to 1 if you have the header file. */ #define HAVE_STDLIB_H 1 /* Define to 1 if you have the `strerror' function. */ #define HAVE_STRERROR 1 /* Define to 1 if you have the header file. */ /* #undef HAVE_STRINGS_H */ /* Define to 1 if you have the header file. */ #define HAVE_STRING_H 1 /* Define to 1 if you have the `strtoul' function. */ #define HAVE_STRTOUL 1 /* Define to 1 if you have the header file. */ /* #undef HAVE_SYS_SELECT_H */ /* Define to 1 if you have the header file. */ /* #undef HAVE_SYS_STAT_H */ /* Define to 1 if you have the header file. */ /* #undef HAVE_SYS_TIME_H */ /* Define to 1 if you have the header file. */ /* #undef HAVE_SYS_TYPES_H */ /* Define to 1 if you have the header file. */ /* #undef HAVE_TIME_H */ /* Define to 1 if the system has the type `uint64_t'. */ /* #undef HAVE_UINT64_T */ /* Define to 1 if the system has the type `uintptr_t'. */ #if defined(_MSC_VER) && _MSC_VER >= 1900 # define HAVE_UINTPTR_T 1 #endif /* Define to 1 if you have the header file. */ /* #undef HAVE_UNISTD_H */ /* Define if you have u_intXX_t types but not uintXX_t types. */ /* #undef HAVE_U_INT_TYPES */ /* Define to 1 if `vector_size' vector types are usable. */ /* #undef HAVE_VECTOR_SIZE_VECTOR_TYPES */ /* Define to 1 if you have the `__builtin_shufflevector' function. */ /* #undef HAVE___BUILTIN_SHUFFLEVECTOR */ /* Define to 1 if you have the `__sync_add_and_fetch' function. */ /* #undef HAVE___SYNC_ADD_AND_FETCH */ /* Define to write GIFs to stdout even when stdout is a terminal. */ /* #undef OUTPUT_GIF_TO_TERMINAL */ /* Name of package */ #define PACKAGE "gifsicle" /* Define to the address where bug reports for this package should be sent. */ #define PACKAGE_BUGREPORT "" /* Define to the full name of this package. */ #define PACKAGE_NAME "gifsicle" /* Define to the full name and version of this package. */ #define PACKAGE_STRING "gifsicle 1.96" /* Define to the one symbol short name of this package. */ #define PACKAGE_TARNAME "gifsicle" /* Define to the home page for this package. */ #define PACKAGE_URL "" /* Define to the version of this package. */ #define PACKAGE_VERSION "1.96" /* Pathname separator character ('/' on Unix). */ #define PATHNAME_SEPARATOR '\\' /* Define to a function that returns a random number. */ #define RANDOM rand /* The size of `float', as computed by sizeof. */ #define SIZEOF_FLOAT 4 /* The size of `unsigned int', as computed by sizeof. */ #define SIZEOF_UNSIGNED_INT 4 /* The size of `unsigned long', as computed by sizeof. */ #define SIZEOF_UNSIGNED_LONG 4 /* The size of `void *', as computed by sizeof. */ #ifdef _WIN64 #define SIZEOF_VOID_P 8 #else #define SIZEOF_VOID_P 4 #endif /* Define to 1 if you have the ANSI C header files. */ #define STDC_HEADERS 1 /* Version number of package */ #define VERSION "1.96 (Windows)" /* Define if X is not available. */ #define X_DISPLAY_MISSING 1 /* Define to empty if `const' does not conform to ANSI C. */ /* #undef const */ /* Define to `__inline__' or `__inline' if that's what the C compiler calls it, or to nothing if 'inline' is not supported under any name. */ #ifndef __cplusplus # ifndef inline # define inline __inline # endif #endif /* Windows doesn't have popen, but it does have _popen. */ #define popen _popen #define pclose _pclose #include #ifdef __cplusplus extern "C" { #endif /* Use the clean-failing malloc library in fmalloc.c. */ #define GIF_ALLOCATOR_DEFINED 1 #define Gif_Free free /* Prototype strerror if we don't have it. */ #ifndef HAVE_STRERROR char *strerror(int errno); #endif #ifdef __cplusplus } /* Get rid of a possible inline macro under C++. */ # define inline inline #endif /* Need _setmode under MS-DOS, to set stdin/stdout to binary mode */ /* Need _fsetmode under OS/2 for the same reason */ /* Windows has _isatty and _snprintf, not the normal versions */ #if defined(_MSDOS) || defined(_WIN32) || defined(__EMX__) || defined(__DJGPP__) # include # include # define isatty _isatty # if defined(_MSC_VER) && _MSC_VER < 1900 # define snprintf _snprintf # endif #endif #endif /* GIFSICLE_CONFIG_H */ gifsicle-1.96/src/xform.c000066400000000000000000001312101475770763400153460ustar00rootroot00000000000000/* xform.c - Image transformation functions for gifsicle. Copyright (C) 1997-2025 Eddie Kohler, ekohler@gmail.com This file is part of gifsicle. Gifsicle is free software. It is distributed under the GNU Public License, version 2; you can copy, distribute, or alter it at will, as long as this notice is kept intact and this source code is made available. There is no warranty, express or implied. */ #include #include "gifsicle.h" #include #include #include #include #include #include #include #include "kcolor.h" #if HAVE_UNISTD_H # include #endif #if HAVE_SYS_TYPES_H && HAVE_SYS_STAT_H # include # include #endif #ifndef M_PI /* -std=c89 does not define M_PI */ # define M_PI 3.14159265358979323846 #endif /****** * color transforms **/ Gt_ColorTransform * append_color_transform(Gt_ColorTransform *list, color_transform_func func, void *data) { Gt_ColorTransform *trav; Gt_ColorTransform *xform = Gif_New(Gt_ColorTransform); xform->next = 0; xform->func = func; xform->data = data; for (trav = list; trav && trav->next; trav = trav->next) ; if (trav) { trav->next = xform; return list; } else return xform; } Gt_ColorTransform * delete_color_transforms(Gt_ColorTransform *list, color_transform_func func) { Gt_ColorTransform *prev = 0, *trav = list; while (trav) { Gt_ColorTransform *next = trav->next; if (trav->func == func) { if (prev) prev->next = next; else list = next; Gif_Delete(trav); } else prev = trav; trav = next; } return list; } void apply_color_transforms(Gt_ColorTransform *list, Gif_Stream *gfs) { int i; Gt_ColorTransform *xform; for (xform = list; xform; xform = xform->next) { if (gfs->global) xform->func(gfs->global, xform->data); for (i = 0; i < gfs->nimages; i++) if (gfs->images[i]->local) xform->func(gfs->images[i]->local, xform->data); } } typedef struct Gt_ColorChange { struct Gt_ColorChange *next; Gif_Color old_color; Gif_Color new_color; } Gt_ColorChange; void color_change_transformer(Gif_Colormap *gfcm, void *thunk) { int i, have; Gt_ColorChange *first_change = (Gt_ColorChange *)thunk; Gt_ColorChange *change; /* change colors named by color */ for (i = 0; i < gfcm->ncol; i++) for (change = first_change; change; change = change->next) { if (!change->old_color.haspixel) have = GIF_COLOREQ(&gfcm->col[i], &change->old_color); else have = (change->old_color.pixel == (uint32_t)i); if (have) { gfcm->col[i] = change->new_color; break; /* ignore remaining color changes */ } } } Gt_ColorTransform * append_color_change(Gt_ColorTransform *list, Gif_Color old_color, Gif_Color new_color) { Gt_ColorTransform *xform; Gt_ColorChange *change = Gif_New(Gt_ColorChange); change->old_color = old_color; change->new_color = new_color; change->next = 0; for (xform = list; xform && xform->next; xform = xform->next) ; if (!xform || xform->func != &color_change_transformer) return append_color_transform(list, &color_change_transformer, change); else { Gt_ColorChange *prev = (Gt_ColorChange *)(xform->data); while (prev->next) prev = prev->next; prev->next = change; return list; } } void pipe_color_transformer(Gif_Colormap *gfcm, void *thunk) { int i, status; FILE *f; Gif_Color *col = gfcm->col; Gif_Colormap *new_cm = 0; char *command = (char *)thunk; #ifdef HAVE_MKSTEMP # ifdef P_tmpdir char tmp_file[] = P_tmpdir "/gifsicle.XXXXXX"; # else char tmp_file[] = "/tmp/gifsicle.XXXXXX"; # endif #else char *tmp_file = tmpnam(0); #endif char *new_command; size_t new_command_sz; #ifdef HAVE_MKSTEMP { mode_t old_mode = umask(077); if (mkstemp(tmp_file) < 0) fatal_error("can%,t create temporary file!"); umask(old_mode); } #else if (!tmp_file) fatal_error("can%,t create temporary file!"); #endif new_command_sz = strlen(command) + strlen(tmp_file) + 4; new_command = Gif_NewArray(char, new_command_sz); snprintf(new_command, new_command_sz, "%s >%s", command, tmp_file); f = popen(new_command, "w"); if (!f) fatal_error("can%,t run color transformation command: %s", strerror(errno)); Gif_DeleteArray(new_command); for (i = 0; i < gfcm->ncol; i++) fprintf(f, "%d %d %d\n", col[i].gfc_red, col[i].gfc_green, col[i].gfc_blue); errno = 0; status = pclose(f); if (status < 0) { error(1, "color transformation error: %s", strerror(errno)); goto done; } else if (status > 0) { error(1, "color transformation command failed"); goto done; } f = fopen(tmp_file, "r"); if (!f || feof(f)) { error(1, "color transformation command generated no output", command); if (f) fclose(f); goto done; } new_cm = read_colormap_file("", f); fclose(f); if (new_cm) { int nc = new_cm->ncol; if (nc < gfcm->ncol) { nc = gfcm->ncol; warning(1, "too few colors in color transformation results"); } else if (nc > gfcm->ncol) warning(1, "too many colors in color transformation results"); for (i = 0; i < nc; i++) col[i] = new_cm->col[i]; } done: remove(tmp_file); Gif_DeleteColormap(new_cm); } void combine_crop(Gt_Crop* dstcrop, const Gt_Crop* srccrop, const Gif_Image* gfi) { int cl = srccrop->x - gfi->left, cr = cl + srccrop->w, ct = srccrop->y - gfi->top, cb = ct + srccrop->h, dl = cl > 0 ? cl : 0, dr = cr < gfi->width ? cr : gfi->width, dt = ct > 0 ? ct : 0, db = cb < gfi->height ? cb : gfi->height; if (dl < dr) { dstcrop->x = dl; dstcrop->w = dr - dl; } else { dstcrop->x = (cl <= 0 ? 0 : srccrop->w - 1) + (srccrop->left_offset - gfi->left); dstcrop->w = 0; } if (dt < db) { dstcrop->y = dt; dstcrop->h = db - dt; } else { dstcrop->y = (ct <= 0 ? 0 : srccrop->h - 1) + (srccrop->top_offset - gfi->top); dstcrop->h = 0; } } /***** * crop image; returns true if the image exists **/ int crop_image(Gif_Image* gfi, Gt_Frame* fr, int preserve_total_crop) { Gt_Crop c; int j; combine_crop(&c, fr->crop, gfi); fr->left_offset = fr->crop->left_offset; fr->top_offset = fr->crop->top_offset; if (c.w > 0 && c.h > 0 && gfi->img) { uint8_t** old_img = gfi->img; gfi->img = Gif_NewArray(uint8_t *, c.h + 1); for (j = 0; j < c.h; j++) gfi->img[j] = old_img[c.y + j] + c.x; gfi->img[c.h] = 0; Gif_DeleteArray(old_img); gfi->left += c.x - fr->left_offset; gfi->top += c.y - fr->top_offset; gfi->width = c.w; gfi->height = c.h; } else if (preserve_total_crop) { Gif_MakeImageEmpty(gfi); } else { Gif_DeleteArray(gfi->img); gfi->img = 0; gfi->width = gfi->height = 0; } return gfi->img != 0; } /***** * flip and rotate **/ void flip_image(Gif_Image* gfi, Gt_Frame *fr, int is_vert) { int x, y; int width = gfi->width; int height = gfi->height; uint8_t **img = gfi->img; /* horizontal flips */ if (!is_vert) { uint8_t *buffer = Gif_NewArray(uint8_t, width); uint8_t *trav; for (y = 0; y < height; y++) { memcpy(buffer, img[y], width); trav = img[y] + width - 1; for (x = 0; x < width; x++) *trav-- = buffer[x]; } gfi->left = fr->stream->screen_width - (gfi->left + width); if (fr->crop) fr->left_offset = fr->stream->screen_width - (fr->left_offset + fr->crop->w); Gif_DeleteArray(buffer); } /* vertical flips */ if (is_vert) { uint8_t **buffer = Gif_NewArray(uint8_t *, height); memcpy(buffer, img, height * sizeof(uint8_t *)); for (y = 0; y < height; y++) img[y] = buffer[height - y - 1]; gfi->top = fr->stream->screen_height - (gfi->top + height); if (fr->crop) fr->top_offset = fr->stream->screen_height - (fr->top_offset + fr->crop->h); Gif_DeleteArray(buffer); } } void rotate_image(Gif_Image* gfi, Gt_Frame* fr, int rotation) { int x, y; int width = gfi->width; int height = gfi->height; int ntop, nleft, nbottom, nright; uint8_t **img = gfi->img; uint8_t *new_data = Gif_NewArray(uint8_t, (unsigned) width * (unsigned) height); uint8_t *trav = new_data; /* this function can only rotate by 90 or 270 degrees */ assert(rotation == 1 || rotation == 3); if (rotation == 1) { for (x = 0; x < width; x++) for (y = height - 1; y >= 0; y--) *trav++ = img[y][x]; } else { for (x = width - 1; x >= 0; x--) for (y = 0; y < height; y++) *trav++ = img[y][x]; } if (rotation == 1) { ntop = gfi->left; nleft = fr->stream->screen_height - (gfi->top + height); if (fr->crop) { x = fr->left_offset; fr->left_offset = fr->stream->screen_height - (fr->top_offset + fr->crop->h); fr->top_offset = x; } } else { ntop = fr->stream->screen_width - (gfi->left + width); nleft = gfi->top; if (fr->crop) { y = fr->top_offset; fr->top_offset = fr->stream->screen_width - (fr->left_offset + fr->crop->w); fr->left_offset = y; } } nbottom = ntop + width; nright = nleft + height; if (nbottom <= 0 || nright <= 0) { ntop = nleft = nbottom = nright = 0; } else if (ntop < 0 || nleft < 0) { int nwidth = nright - (nleft < 0 ? 0 : nleft); uint8_t *xdata; xdata = Gif_NewArray(uint8_t, (unsigned) (nright - nleft) * (unsigned) (nbottom - ntop)); for (y = ntop < 0 ? -ntop : 0; y < width; ++y) { memcpy(xdata + y * nwidth, new_data + y * height + (nleft < 0 ? -nleft : 0), nwidth); } Gif_DeleteArray(new_data); new_data = xdata; ntop = ntop < 0 ? 0 : ntop; nleft = nleft < 0 ? 0 : nleft; } Gif_ReleaseUncompressedImage(gfi); gfi->width = nright - nleft; gfi->height = nbottom - ntop; gfi->left = nleft; gfi->top = ntop; Gif_SetUncompressedImage(gfi, new_data, Gif_Free, 0); } /***** * scale **/ /* kcscreen: A frame buffer containing `kacolor`s (one per pixel). Kcolors with components < 0 represent transparency. */ typedef struct { kacolor* data; /* data buffer: (x,y) in data[y*width + x] */ kacolor* scratch; /* scratch buffer, used for previous disposal */ unsigned width; /* screen width */ unsigned height; /* screen height */ kacolor bg; /* background color */ } kcscreen; /* initialize `kcs` to an empty state */ static void kcscreen_clear(kcscreen* kcs) { kcs->data = kcs->scratch = NULL; } /* initialize `kcs` for stream `gfs`. Screen size is `sw`x`sh`; if either component is <= 0, the value of `gfs->screen_*` is taken. */ static void kcscreen_init(kcscreen* kcs, Gif_Stream* gfs, int sw, int sh) { unsigned i, sz; assert(!kcs->data && !kcs->scratch); kcs->width = sw <= 0 ? gfs->screen_width : sw; kcs->height = sh <= 0 ? gfs->screen_height : sh; sz = kcs->width * kcs->height; kcs->data = Gif_NewArray(kacolor, sz); if ((gfs->nimages == 0 || gfs->images[0]->transparent < 0) && gfs->global && gfs->background < gfs->global->ncol) { kcs->bg.k = kc_makegfcg(&gfs->global->col[gfs->background]); kcs->bg.a[3] = KC_MAX; } else kcs->bg.a[0] = kcs->bg.a[1] = kcs->bg.a[2] = kcs->bg.a[3] = 0; for (i = 0; i != sz; ++i) kcs->data[i] = kcs->bg; } /* free memory associated with `kcs` */ static void kcscreen_cleanup(kcscreen* kcs) { Gif_DeleteArray(kcs->data); Gif_DeleteArray(kcs->scratch); } /* apply image `gfi` to screen `kcs`, using the color array `ks` */ static void kcscreen_apply(kcscreen* kcs, const Gif_Image* gfi, const kcolor* ks) { int x, y; assert((unsigned) gfi->left + gfi->width <= kcs->width); assert((unsigned) gfi->top + gfi->height <= kcs->height); if (gfi->disposal == GIF_DISPOSAL_PREVIOUS) { if (!kcs->scratch) kcs->scratch = Gif_NewArray(kacolor, kcs->width * kcs->height); for (y = gfi->top; y != gfi->top + gfi->height; ++y) memcpy(&kcs->scratch[y * kcs->width + gfi->left], &kcs->data[y * kcs->width + gfi->left], sizeof(kacolor) * gfi->width); } for (y = gfi->top; y != gfi->top + gfi->height; ++y) { const uint8_t* linein = gfi->img[y - gfi->top]; kacolor* lineout = &kcs->data[y * kcs->width + gfi->left]; for (x = 0; x != gfi->width; ++x) if (linein[x] != gfi->transparent) { lineout[x].k = ks[linein[x]]; lineout[x].a[3] = KC_MAX; } } } /* dispose of image `gfi`, which was previously applied by kcscreen_apply */ static void kcscreen_dispose(kcscreen* kcs, const Gif_Image* gfi) { int x, y; assert((unsigned) gfi->left + gfi->width <= kcs->width); assert((unsigned) gfi->top + gfi->height <= kcs->height); if (gfi->disposal == GIF_DISPOSAL_PREVIOUS) { for (y = gfi->top; y != gfi->top + gfi->height; ++y) memcpy(&kcs->data[y * kcs->width + gfi->left], &kcs->scratch[y * kcs->width + gfi->left], sizeof(kacolor) * gfi->width); } else if (gfi->disposal == GIF_DISPOSAL_BACKGROUND) { for (y = gfi->top; y != gfi->top + gfi->height; ++y) for (x = gfi->left; x != gfi->left + gfi->width; ++x) kcs->data[y * kcs->width + x] = kcs->bg; } } /* ksscreen: A frame buffer containing `scale_color`s (one per pixel). Kcolors with components < 0 represent transparency. */ typedef struct { scale_color* data; /* data buffer: (x,y) in data[y*width + x] */ scale_color* scratch; /* scratch buffer, used for previous disposal */ unsigned width; /* screen width */ unsigned height; /* screen height */ scale_color bg; /* background color */ } ksscreen; /* initialize `kss` to an empty state */ static void ksscreen_clear(ksscreen* kss) { kss->data = kss->scratch = NULL; } /* initialize `kss` for stream `gfs`. Screen size is `sw`x`sh`; if either component is <= 0, the value of `gfs->screen_*` is taken. */ static void ksscreen_init(ksscreen* kss, Gif_Stream* gfs, int sw, int sh) { unsigned i, sz; assert(!kss->data && !kss->scratch); kss->width = sw <= 0 ? gfs->screen_width : sw; kss->height = sh <= 0 ? gfs->screen_height : sh; sz = kss->width * kss->height; kss->data = Gif_NewArray(scale_color, sz); if ((gfs->nimages == 0 || gfs->images[0]->transparent < 0) && gfs->global && gfs->background < gfs->global->ncol) { kss->bg = sc_makekc(kc_makegfcg(&gfs->global->col[gfs->background])); } else sc_clear(&kss->bg); for (i = 0; i != sz; ++i) kss->data[i] = kss->bg; } /* free memory associated with `kss` */ static void ksscreen_cleanup(ksscreen* kss) { Gif_DeleteArray(kss->data); Gif_DeleteArray(kss->scratch); } /* apply image `gfi` to screen `kss`, using the color array `ks` */ static void ksscreen_apply(ksscreen* kss, const Gif_Image* gfi, const kcolor* ks) { int x, y; assert((unsigned) gfi->left + gfi->width <= kss->width); assert((unsigned) gfi->top + gfi->height <= kss->height); if (gfi->disposal == GIF_DISPOSAL_PREVIOUS) { if (!kss->scratch) kss->scratch = Gif_NewArray(scale_color, kss->width * kss->height); for (y = gfi->top; y != gfi->top + gfi->height; ++y) memcpy(&kss->scratch[y * kss->width + gfi->left], &kss->data[y * kss->width + gfi->left], sizeof(scale_color) * gfi->width); } for (y = gfi->top; y != gfi->top + gfi->height; ++y) { const uint8_t* linein = gfi->img[y - gfi->top]; scale_color* lineout = &kss->data[y * kss->width + gfi->left]; for (x = 0; x != gfi->width; ++x) if (linein[x] != gfi->transparent) lineout[x] = sc_makekc(ks[linein[x]]); } } /* dispose of image `gfi`, which was previously applied by ksscreen_apply */ static void ksscreen_dispose(ksscreen* kss, const Gif_Image* gfi) { int x, y; assert((unsigned) gfi->left + gfi->width <= kss->width); assert((unsigned) gfi->top + gfi->height <= kss->height); if (gfi->disposal == GIF_DISPOSAL_PREVIOUS) { for (y = gfi->top; y != gfi->top + gfi->height; ++y) memcpy(&kss->data[y * kss->width + gfi->left], &kss->scratch[y * kss->width + gfi->left], sizeof(scale_color) * gfi->width); } else if (gfi->disposal == GIF_DISPOSAL_BACKGROUND) { for (y = gfi->top; y != gfi->top + gfi->height; ++y) for (x = gfi->left; x != gfi->left + gfi->width; ++x) kss->data[y * kss->width + x] = kss->bg; } } typedef struct { float w; int ipos; int opos; } scale_weight; typedef struct { scale_weight* ws; int n; } scale_weightset; typedef struct { Gif_Stream* gfs; Gif_Image* gfi; int imageno; kd3_tree* kd3; ksscreen iscr; kcscreen oscr; kcscreen xscr; float oxf; /* (input width) / (output width) */ float oyf; /* (input height) / (output height) */ float ixf; /* (output width) / (input width) */ float iyf; /* (output height) / (input height) */ scale_weightset xweights; scale_weightset yweights; kd3_tree global_kd3; kd3_tree local_kd3; unsigned max_desired_dist; int scale_colors; } scale_context; #if ENABLE_THREADS static pthread_mutex_t global_colormap_lock = PTHREAD_MUTEX_INITIALIZER; #define GLOBAL_COLORMAP_LOCK() pthread_mutex_lock(&global_colormap_lock) #define GLOBAL_COLORMAP_UNLOCK() pthread_mutex_unlock(&global_colormap_lock) #else #define GLOBAL_COLORMAP_LOCK() /* nada */ #define GLOBAL_COLORMAP_UNLOCK() /* nada */ #endif static void sctx_init(scale_context* sctx, Gif_Stream* gfs, int nw, int nh) { sctx->gfs = gfs; sctx->gfi = NULL; sctx->global_kd3.ks = NULL; sctx->local_kd3.ks = NULL; ksscreen_clear(&sctx->iscr); kcscreen_clear(&sctx->oscr); kcscreen_clear(&sctx->xscr); sctx->iscr.width = gfs->screen_width; sctx->iscr.height = gfs->screen_height; sctx->oscr.width = nw; sctx->oscr.height = nh; sctx->xscr.width = nw; sctx->xscr.height = nh; sctx->ixf = (double) nw / gfs->screen_width; sctx->iyf = (double) nh / gfs->screen_height; sctx->oxf = gfs->screen_width / (double) nw; sctx->oyf = gfs->screen_height / (double) nh; sctx->xweights.ws = sctx->yweights.ws = NULL; sctx->xweights.n = sctx->yweights.n = 0; sctx->max_desired_dist = 16000; sctx->scale_colors = 0; sctx->kd3 = NULL; sctx->imageno = 0; } static void sctx_cleanup(scale_context* sctx) { if (sctx->global_kd3.ks) kd3_cleanup(&sctx->global_kd3); ksscreen_cleanup(&sctx->iscr); kcscreen_cleanup(&sctx->oscr); kcscreen_cleanup(&sctx->xscr); Gif_DeleteArray(sctx->xweights.ws); Gif_DeleteArray(sctx->yweights.ws); } static void scale_image_data_point(scale_context* sctx, Gif_Image* gfo) { Gif_Image* gfi = sctx->gfi; uint8_t* data = gfo->image_data; int xo, yo; uint16_t* xoff = Gif_NewArray(uint16_t, gfo->width); for (xo = 0; xo != gfo->width; ++xo) xoff[xo] = (int) ((gfo->left + xo + 0.5) * sctx->oxf) - gfi->left; for (yo = 0; yo != gfo->height; ++yo) { int yi = (int) ((gfo->top + yo + 0.5) * sctx->oyf) - gfi->top; const uint8_t* in_line = gfi->img[yi]; for (xo = 0; xo != gfo->width; ++xo, ++data) *data = in_line[xoff[xo]]; } Gif_DeleteArray(xoff); } static void scale_image_update_global_kd3(scale_context* sctx) { Gif_Colormap* gfcm = sctx->gfs->global; assert(sctx->kd3 == &sctx->global_kd3); while (sctx->kd3->nitems < gfcm->ncol) { Gif_Color* gfc = &gfcm->col[sctx->kd3->nitems]; kd3_add8g(sctx->kd3, gfc->gfc_red, gfc->gfc_green, gfc->gfc_blue); } } static void scale_image_prepare(scale_context* sctx) { if (sctx->gfi->local) { sctx->kd3 = &sctx->local_kd3; kd3_init_build(sctx->kd3, NULL, sctx->gfi->local); } else { sctx->kd3 = &sctx->global_kd3; if (!sctx->kd3->ks) kd3_init(sctx->kd3, NULL); GLOBAL_COLORMAP_LOCK(); scale_image_update_global_kd3(sctx); GLOBAL_COLORMAP_UNLOCK(); if (!sctx->kd3->tree) kd3_build(sctx->kd3); kd3_enable_all(sctx->kd3); } if (sctx->gfi->transparent >= 0 && sctx->gfi->transparent < sctx->kd3->nitems) kd3_disable(sctx->kd3, sctx->gfi->transparent); /* apply image to sctx->iscr */ if (!sctx->iscr.data) { /* first image, set up screens */ ksscreen_init(&sctx->iscr, sctx->gfs, 0, 0); kcscreen_init(&sctx->oscr, sctx->gfs, sctx->oscr.width, sctx->oscr.height); kcscreen_init(&sctx->xscr, sctx->gfs, sctx->xscr.width, sctx->xscr.height); } ksscreen_apply(&sctx->iscr, sctx->gfi, sctx->kd3->ks); } static void scale_image_output_row(scale_context* sctx, scale_color* sc, Gif_Image* gfo, int yo) { int k, xo; kacolor* oscr = &sctx->xscr.data[sctx->xscr.width * (yo + gfo->top) + gfo->left]; for (xo = 0; xo != gfo->width; ++xo) if (sc[xo].a[3] <= (int) (KC_MAX / 4)) oscr[xo] = kac_transparent(); else { /* don't effectively mix partially transparent pixels with black */ if (sc[xo].a[3] <= (int) (KC_MAX * 31 / 32)) SCVEC_MULF(sc[xo], KC_MAX / sc[xo].a[3]); /* find closest color */ for (k = 0; k != 3; ++k) { int v = (int) (sc[xo].a[k] + 0.5); oscr[xo].a[k] = KC_CLAMPV(v); } oscr[xo].a[3] = KC_MAX; } } static int scale_image_add_colors(scale_context* sctx, Gif_Image* gfo) { kchist kch; kcdiversity div; Gif_Color gfc; Gif_Colormap* gfcm = sctx->gfi->local ? sctx->gfi->local : sctx->gfs->global; int yo, xo, i, nadded; kchist_init(&kch); for (yo = 0; yo != gfo->height; ++yo) { kacolor* xscr = &sctx->xscr.data[sctx->xscr.width * (yo + gfo->top) + gfo->left]; for (xo = 0; xo != gfo->width; ++xo) if (xscr[xo].a[3]) kchist_add(&kch, xscr[xo].k, 1); } for (i = 0; i != gfcm->ncol; ++i) kchist_add(&kch, kc_makegfcg(&gfcm->col[i]), (kchist_count_t) -1); kchist_compress(&kch); kcdiversity_init(&div, &kch, 0); for (i = 0; i != kch.n && i != gfcm->ncol && kch.h[i].count == (kchist_count_t) -1; ++i) kcdiversity_choose(&div, i, 0); nadded = 0; while (gfcm->ncol < sctx->scale_colors) { int chosen = kcdiversity_find_diverse(&div, 0); if (chosen >= kch.n || div.min_dist[chosen] <= sctx->max_desired_dist) break; kcdiversity_choose(&div, chosen, 0); gfc = kc_togfcg(kch.h[chosen].ka.k); Gif_AddColor(gfcm, &gfc, gfcm->ncol); kd3_add8g(sctx->kd3, gfc.gfc_red, gfc.gfc_green, gfc.gfc_blue); ++nadded; } kcdiversity_cleanup(&div); kchist_cleanup(&kch); return nadded != 0; } static void scale_image_complete(scale_context* sctx, Gif_Image* gfo) { int yo, xo, transparent = sctx->gfi->transparent; unsigned dist, dist2, max_dist; retry: max_dist = 0; /* look up palette for output screen */ for (yo = 0; yo != gfo->height; ++yo) { uint8_t* data = gfo->img[yo]; kacolor* xscr = &sctx->xscr.data[sctx->xscr.width * (yo + gfo->top) + gfo->left]; kacolor* oscr = &sctx->oscr.data[sctx->oscr.width * (yo + gfo->top) + gfo->left]; for (xo = 0; xo != gfo->width; ++xo) if (xscr[xo].a[3]) { data[xo] = kd3_closest_transformed(sctx->kd3, xscr[xo].k, &dist); /* maybe previous color is actually closer to desired */ if (transparent >= 0 && oscr[xo].a[3]) { dist2 = kc_distance(oscr[xo].k, xscr[xo].k); if (dist2 <= dist) { data[xo] = transparent; dist = dist2; } } max_dist = dist > max_dist ? dist : max_dist; } else data[xo] = transparent; } /* if the global colormap has changed, we must retry (this can only happen if ENABLE_THREADS) */ if (!sctx->gfi->local) { Gif_Colormap* gfcm = sctx->gfs->global; GLOBAL_COLORMAP_LOCK(); if (gfcm->ncol > sctx->kd3->nitems) { scale_image_update_global_kd3(sctx); GLOBAL_COLORMAP_UNLOCK(); goto retry; } } /* maybe add some colors */ if (max_dist > sctx->max_desired_dist) { Gif_Colormap* gfcm = sctx->gfi->local ? sctx->gfi->local : sctx->gfs->global; if (gfcm->ncol < sctx->scale_colors && scale_image_add_colors(sctx, gfo)) { if (!sctx->gfi->local) GLOBAL_COLORMAP_UNLOCK(); goto retry; } } if (!sctx->gfi->local) GLOBAL_COLORMAP_UNLOCK(); /* apply disposal to sctx->iscr and sctx->oscr */ if (sctx->imageno != sctx->gfs->nimages - 1) { ksscreen_dispose(&sctx->iscr, sctx->gfi); if (sctx->gfi->disposal == GIF_DISPOSAL_BACKGROUND) kcscreen_dispose(&sctx->oscr, gfo); else if (sctx->gfi->disposal != GIF_DISPOSAL_PREVIOUS) kcscreen_apply(&sctx->oscr, gfo, sctx->kd3->ks); } if (sctx->gfi->local) kd3_cleanup(sctx->kd3); } typedef struct pixel_range { int lo; int hi; } pixel_range; typedef struct pixel_range2 { float bounds[2]; int lo; int hi; } pixel_range2; static inline pixel_range make_pixel_range(int xi, int maxi, int maxo, float f) { pixel_range pr; pr.lo = (int) (xi * f); if (xi + 1 == maxi) pr.hi = maxo; else pr.hi = (int) ((xi + 1) * f); if (pr.hi == pr.lo) ++pr.hi; return pr; } static inline pixel_range2 make_pixel_range2(int xi, int maxi, int maxo, float f) { pixel_range2 pr; pr.bounds[0] = xi * f; pr.bounds[1] = (xi + 1) * f; pr.lo = (int) pr.bounds[0]; if (xi + 1 == maxi) pr.hi = maxo; else pr.hi = (int) ceil(pr.bounds[1]); if (pr.hi == pr.lo) ++pr.hi; return pr; } static void scale_image_data_box(scale_context* sctx, Gif_Image* gfo) { uint16_t* xoff = Gif_NewArray(uint16_t, sctx->iscr.width); scale_color* sc = Gif_NewArray(scale_color, gfo->width); int* nsc = Gif_NewArray(int, gfo->width); int xi0, xi1, xo, yo, i, j; scale_image_prepare(sctx); /* develop scale mapping of input -> output pixels */ for (xo = 0; xo != gfo->width; ++xo) { pixel_range xpr = make_pixel_range(xo + gfo->left, sctx->oscr.width, sctx->iscr.width, sctx->oxf); for (i = xpr.lo; i != xpr.hi; ++i) xoff[i] = xo; } xi0 = make_pixel_range(gfo->left, sctx->oscr.width, sctx->iscr.width, sctx->oxf).lo; xi1 = make_pixel_range(gfo->left + gfo->width - 1, sctx->oscr.width, sctx->iscr.width, sctx->oxf).hi; /* walk over output image */ for (yo = 0; yo != gfo->height; ++yo) { pixel_range ypr = make_pixel_range(yo + gfo->top, sctx->oscr.height, sctx->iscr.height, sctx->oyf); for (xo = 0; xo != gfo->width; ++xo) sc_clear(&sc[xo]); for (xo = 0; xo != gfo->width; ++xo) nsc[xo] = 0; /* collect input mixes */ for (j = ypr.lo; j != ypr.hi; ++j) { const scale_color* indata = &sctx->iscr.data[sctx->iscr.width*j]; for (i = xi0; i != xi1; ++i) { ++nsc[xoff[i]]; SCVEC_ADDV(sc[xoff[i]], indata[i]); } } /* scale channels */ for (xo = 0; xo != gfo->width; ++xo) SCVEC_DIVF(sc[xo], (float) nsc[xo]); /* generate output */ scale_image_output_row(sctx, sc, gfo, yo); } scale_image_complete(sctx, gfo); Gif_DeleteArray(xoff); Gif_DeleteArray(sc); Gif_DeleteArray(nsc); } static inline float mix_factor(int val, const float bounds[2]) { if (val < bounds[0] && bounds[1] < val + 1) return bounds[1] - bounds[0]; else if (val < bounds[0]) return 1 - (bounds[0] - val); else if (bounds[1] < val + 1) return bounds[1] - val; else return 1.0; } typedef struct scale_mixinfo { uint16_t xi; uint16_t xo; float f; } scale_mixinfo; static void scale_image_data_mix(scale_context* sctx, Gif_Image* gfo) { int xo, yo, i, j, nmix = 0, mixcap = sctx->iscr.width * 2; scale_color* sc = Gif_NewArray(scale_color, gfo->width); scale_mixinfo* mix = Gif_NewArray(scale_mixinfo, mixcap); scale_image_prepare(sctx); /* construct mixinfo */ for (xo = 0; xo != gfo->width; ++xo) { pixel_range2 xpr = make_pixel_range2(xo + gfo->left, sctx->oscr.width, sctx->iscr.width, sctx->oxf); while (nmix + xpr.hi - xpr.lo > mixcap) { mixcap *= 2; Gif_ReArray(mix, scale_mixinfo, mixcap); } for (i = xpr.lo; i < xpr.hi; ++i, ++nmix) { mix[nmix].xi = i; mix[nmix].xo = xo; mix[nmix].f = mix_factor(i, xpr.bounds); } } for (yo = 0; yo != gfo->height; ++yo) { pixel_range2 ypr = make_pixel_range2(yo + gfo->top, sctx->oscr.height, sctx->iscr.height, sctx->oyf); for (xo = 0; xo != gfo->width; ++xo) sc_clear(&sc[xo]); /* collect input mixes */ for (j = ypr.lo; j < ypr.hi; ++j) { float jf = mix_factor(j, ypr.bounds) * sctx->ixf * sctx->iyf; const scale_color* indata = &sctx->iscr.data[sctx->iscr.width*j]; for (i = 0; i != nmix; ++i) SCVEC_ADDVxF(sc[mix[i].xo], indata[mix[i].xi], (float) (mix[i].f * jf)); } /* generate output */ scale_image_output_row(sctx, sc, gfo, yo); } scale_image_complete(sctx, gfo); Gif_DeleteArray(sc); Gif_DeleteArray(mix); } static void scale_weightset_add(scale_weightset* wset, int ipos, int opos, double w) { if (wset->n && wset->ws[wset->n - 1].ipos == ipos && wset->ws[wset->n - 1].opos == opos) wset->ws[wset->n - 1].w += w; else { if (!wset->ws) wset->ws = Gif_NewArray(scale_weight, 256); else if (wset->n > 128 && (wset->n & (wset->n - 1)) == 0) Gif_ReArray(wset->ws, scale_weight, wset->n * 2); wset->ws[wset->n].w = w; wset->ws[wset->n].ipos = ipos; wset->ws[wset->n].opos = opos; ++wset->n; } } static inline double scale_weight_cubic(double x, double b, double c) { x = fabs(x); if (x < 1) return ((12 - 9*b - 6*c) * x*x*x + (-18 + 12*b + 6*c) * x*x + (6 - 2*b)) / 6; else if (x < 2) return ((-b - 6*c) * x*x*x + (6*b + 30*c) * x*x + (-12*b - 48*c) * x + (8*b + 24*c)) / 6; else return 0; } static inline double scale_weight_catrom(double x) { return scale_weight_cubic(x, 0, 0.5); } static inline double scale_weight_mitchell(double x) { return scale_weight_cubic(x, 1.0/3, 1.0/3); } static inline double sinc(double x) { if (x <= 0.000000005) return 1; else return sin(M_PI * x) / (M_PI * x); } static inline double scale_weight_lanczos(double x, int lobes) { x = fabs(x); if (x < lobes) return sinc(x) * sinc(x / lobes); else return 0; } static inline double scale_weight_lanczos2(double x) { return scale_weight_lanczos(x, 2); } static inline double scale_weight_lanczos3(double x) { return scale_weight_lanczos(x, 3); } static void scale_weightset_create(scale_weightset* wset, int nin, int nout, double (*weightf)(double), double radius) { double of = (double) nin / nout; double reduction = of > 1.0 ? of : 1.0; int opos; for (opos = 0; opos != nout; ++opos) { double icenter = (opos + 0.5) * of - 0.5; int ipos0 = (int) ceil(icenter - radius * reduction - 0.0001); int ipos1 = (int) floor(icenter + radius * reduction + 0.0001) + 1; int wset0 = wset->n; double sum = 0; for (; ipos0 != ipos1; ++ipos0) { double w = (*weightf)((ipos0 - icenter) / reduction); if (w != 0.0) { int ripos = ipos0 < 0 ? 0 : (ipos0 < nin ? ipos0 : nin - 1); scale_weightset_add(wset, ripos, opos, w); sum += w; } } for (; wset0 != wset->n; ++wset0) wset->ws[wset0].w /= sum; } scale_weightset_add(wset, nin, nout, 0); } static void scale_image_data_weighted(scale_context* sctx, Gif_Image* gfo, double (*weightf)(double), double radius) { int yi, yi0, yi1, xo, yo; scale_color* sc = Gif_NewArray(scale_color, gfo->width); scale_color* kcx = Gif_NewArray(scale_color, gfo->width * sctx->iscr.height); const scale_weight* w, *ww; if (!sctx->xweights.ws) { scale_weightset_create(&sctx->xweights, sctx->iscr.width, sctx->oscr.width, weightf, radius); scale_weightset_create(&sctx->yweights, sctx->iscr.height, sctx->oscr.height, weightf, radius); } scale_image_prepare(sctx); /* scale every input row by the X scaling factor */ { double ycmp = radius * (sctx->oyf > 1 ? sctx->oyf : 1); yi0 = (int) floor(gfo->top * sctx->oyf - ycmp - 0.0001); yi0 = yi0 < 0 ? 0 : yi0; yi1 = (int) ceil((gfo->top + gfo->height) * sctx->oyf + ycmp + 0.0001) + 1; yi1 = yi1 < (int) sctx->iscr.height ? yi1 : (int) sctx->iscr.height; } for (ww = sctx->xweights.ws; ww->opos < gfo->left; ++ww) /* skip */; for (yi = yi0; yi != yi1; ++yi) { const scale_color* iscr = &sctx->iscr.data[sctx->iscr.width * yi]; scale_color* oscr = &kcx[(unsigned) gfo->width * yi]; for (xo = 0; xo != gfo->width; ++xo) sc_clear(&oscr[xo]); for (w = ww; w->opos < gfo->left + gfo->width; ++w) SCVEC_ADDVxF(oscr[w->opos - gfo->left], iscr[w->ipos], w->w); } /* scale the resulting rows by the y factor */ for (w = sctx->yweights.ws; w->opos < gfo->top; ++w) /* skip */; for (yo = 0; yo != gfo->height; ++yo) { for (xo = 0; xo != gfo->width; ++xo) sc_clear(&sc[xo]); for (; w->opos < gfo->top + yo + 1; ++w) { const scale_color* iscr = &kcx[(unsigned) gfo->width * w->ipos]; assert(w->ipos >= yi0 && w->ipos < yi1); for (xo = 0; xo != gfo->width; ++xo) SCVEC_ADDVxF(sc[xo], iscr[xo], w->w); } /* generate output */ scale_image_output_row(sctx, sc, gfo, yo); } scale_image_complete(sctx, gfo); Gif_DeleteArray(sc); Gif_DeleteArray(kcx); } static void scale_image_data_catrom(scale_context* sctx, Gif_Image* gfo) { scale_image_data_weighted(sctx, gfo, scale_weight_catrom, 2); } static void scale_image_data_mitchell(scale_context* sctx, Gif_Image* gfo) { scale_image_data_weighted(sctx, gfo, scale_weight_mitchell, 2); } static void scale_image_data_lanczos2(scale_context* sctx, Gif_Image* gfo) { scale_image_data_weighted(sctx, gfo, scale_weight_lanczos2, 2); } static void scale_image_data_lanczos3(scale_context* sctx, Gif_Image* gfo) { scale_image_data_weighted(sctx, gfo, scale_weight_lanczos3, 3); } static void scale_image(scale_context* sctx, int method) { Gif_Image* gfi = sctx->gfi; Gif_Image gfo; int was_compressed = (gfi->img == 0); /* Fri 9 Jan 1999: Fix problem with resizing animated GIFs: we scaled from left edge of the *subimage* to right edge of the subimage, causing consistency problems when several subimages overlap. Solution: always use scale factors relating to the *whole image* (the screen size). */ /* calculate new width and height based on the four edges (left, right, top, bottom). This is better than simply multiplying the width and height by the scale factors because it avoids roundoff inconsistencies between frames on animated GIFs. Don't allow 0-width or 0-height images; GIF doesn't support them well. */ gfo = *gfi; gfo.img = NULL; gfo.image_data = NULL; gfo.compressed = NULL; gfo.left = (int) (gfi->left * sctx->ixf); gfo.top = (int) (gfi->top * sctx->iyf); gfo.width = (int) ceil((gfi->left + gfi->width) * sctx->ixf) - gfo.left; gfo.height = (int) ceil((gfi->top + gfi->height) * sctx->iyf) - gfo.top; /* account for floating point errors at bottom right edge */ if ((unsigned) gfi->left + gfi->width == sctx->iscr.width) gfo.width = sctx->oscr.width - gfo.left; if ((unsigned) gfi->top + gfi->height == sctx->iscr.height) gfo.height = sctx->oscr.height - gfo.top; /* Point scaling method is special: rather than rendering the input image into a frame buffer, it works on per-frame data. Thus we must verify that the output bounds completely fit into the input image's per-frame data. Using pixel midpoints complicates this task. */ if (method == SCALE_METHOD_POINT) { if (gfo.width && (int) ((gfo.left + 0.5) * sctx->oxf) < gfi->left) ++gfo.left, --gfo.width; if (gfo.width && (int) ((gfo.left + gfo.width - 0.5) * sctx->oxf) >= gfi->left + gfi->width) --gfo.width; if (gfo.height && (int) ((gfo.top + 0.5) * sctx->oyf) < gfi->top) ++gfo.top, --gfo.height; if (gfo.height && (int) ((gfo.top + gfo.height - 0.5) * sctx->oyf) >= gfi->top + gfi->height) --gfo.height; } if (gfo.width == 0 || gfo.height == 0) { gfo.width = gfo.height = 1; Gif_CreateUncompressedImage(&gfo, 0); gfo.image_data[0] = gfo.transparent = 0; gfo.disposal = GIF_DISPOSAL_ASIS; goto done; } if (was_compressed) Gif_UncompressImage(sctx->gfs, gfi); Gif_CreateUncompressedImage(&gfo, 0); if (method == SCALE_METHOD_MIX) scale_image_data_mix(sctx, &gfo); else if (method == SCALE_METHOD_BOX) scale_image_data_box(sctx, &gfo); else if (method == SCALE_METHOD_CATROM) scale_image_data_catrom(sctx, &gfo); else if (method == SCALE_METHOD_LANCZOS2) scale_image_data_lanczos2(sctx, &gfo); else if (method == SCALE_METHOD_LANCZOS3) scale_image_data_lanczos3(sctx, &gfo); else if (method == SCALE_METHOD_MITCHELL) scale_image_data_mitchell(sctx, &gfo); else scale_image_data_point(sctx, &gfo); done: Gif_ReleaseUncompressedImage(gfi); Gif_ReleaseCompressedImage(gfi); *gfi = gfo; if (was_compressed) { Gif_FullCompressImage(sctx->gfs, gfi, &gif_write_info); Gif_ReleaseUncompressedImage(gfi); } } #if ENABLE_THREADS typedef struct { pthread_t threadid; Gif_Stream* gfs; int imageno; int* next_imageno; int nw; int nh; int scale_colors; int method; } scale_thread_context; #if !HAVE___SYNC_ADD_AND_FETCH static pthread_mutex_t next_imageno_lock = PTHREAD_MUTEX_INITIALIZER; #endif void* scale_image_threaded(void* args) { scale_thread_context* ctx = (scale_thread_context*) args; scale_context sctx; sctx_init(&sctx, ctx->gfs, ctx->nw, ctx->nh); sctx.scale_colors = ctx->scale_colors; do { sctx.imageno = ctx->imageno; sctx.gfi = ctx->gfs->images[ctx->imageno]; scale_image(&sctx, ctx->method); #if HAVE___SYNC_ADD_AND_FETCH ctx->imageno = __sync_add_and_fetch(ctx->next_imageno, 1); #else pthread_mutex_lock(&next_imageno_lock); ctx->imageno = ++*ctx->next_imageno; pthread_mutex_unlock(&next_imageno_lock); #endif } while (ctx->imageno < ctx->gfs->nimages); sctx_cleanup(&sctx); return 0; } #endif void resize_dimensions(int* w, int* h, double new_width, double new_height, int flags) { if (new_width < 0.5 && new_height < 0.5) /* do nothing */ return; else if (new_width < 0.5) new_width = *w * new_height / *h; else if (new_height < 0.5) new_height = *h * new_width / *w; if (flags & GT_RESIZE_FIT) { double factor, xfactor, yfactor; if (((flags & GT_RESIZE_FIT_DOWN) && *w < new_width + 0.5 && *h < new_height + 0.5) || ((flags & GT_RESIZE_FIT_UP) && (*w >= new_width + 0.5 || *h >= new_height + 0.5))) return; xfactor = new_width / *w; yfactor = new_height / *h; if ((xfactor < yfactor) == !(flags & GT_RESIZE_MIN_DIMEN)) factor = xfactor; else factor = yfactor; new_width = *w * factor; new_height = *h * factor; } if (new_width >= GIF_MAX_SCREEN_WIDTH + 0.5 || new_height >= GIF_MAX_SCREEN_HEIGHT + 0.5) fatal_error("new image is too large (max size 65535x65535)"); *w = (int) (new_width + 0.5); *h = (int) (new_height + 0.5); /* refuse to create 0-pixel dimensions */ if (*w == 0) *w = 1; if (*h == 0) *h = 1; } void resize_stream(Gif_Stream* gfs, double new_width, double new_height, int flags, int method, int scale_colors) { int nw, nh, nthreads = thread_count, i; (void) i; Gif_CalculateScreenSize(gfs, 0); assert(gfs->nimages > 0); nw = gfs->screen_width; nh = gfs->screen_height; resize_dimensions(&nw, &nh, new_width, new_height, flags); if (nw == gfs->screen_width && nh == gfs->screen_height) return; /* no point to MIX or BOX method if we're expanding the image in both dimens */ if (method == SCALE_METHOD_BOX && gfs->screen_width <= nw && gfs->screen_height <= nh) method = SCALE_METHOD_POINT; if (method == SCALE_METHOD_MIX && gfs->screen_width <= nw && gfs->screen_height <= nh && (nw % gfs->screen_width) == 0 && (nh % gfs->screen_height) == 0) method = SCALE_METHOD_POINT; /* ensure we understand the method */ if (method != SCALE_METHOD_MIX && method != SCALE_METHOD_BOX && method != SCALE_METHOD_CATROM && method != SCALE_METHOD_LANCZOS2 && method != SCALE_METHOD_LANCZOS3 && method != SCALE_METHOD_MITCHELL) method = SCALE_METHOD_POINT; if (nthreads > gfs->nimages) nthreads = gfs->nimages; #if ENABLE_THREADS /* Threaded resize only works if the input image is unoptimized. */ for (i = 0; nthreads > 1 && i < gfs->nimages; ++i) if (gfs->images[i]->left != 0 || gfs->images[i]->top != 0 || gfs->images[i]->width != gfs->screen_width || gfs->images[i]->height != gfs->screen_height || (i != gfs->nimages - 1 && gfs->images[i]->disposal != GIF_DISPOSAL_BACKGROUND && gfs->images[i+1]->transparent >= 0)) { warning(1, "image too complex for multithreaded resize, using 1 thread\n (Try running the GIF through %.)"); nthreads = 1; } if (nthreads > 1) { int next_imageno = nthreads - 1, i; scale_thread_context* ctx = Gif_NewArray(scale_thread_context, nthreads); for (i = 0; i < nthreads; ++i) { ctx[i].gfs = gfs; ctx[i].imageno = i; ctx[i].next_imageno = &next_imageno; ctx[i].nw = nw; ctx[i].nh = nh; ctx[i].scale_colors = scale_colors; ctx[i].method = method; pthread_create(&ctx[i].threadid, NULL, scale_image_threaded, &ctx[i]); } for (i = 0; i < nthreads; ++i) pthread_join(ctx[i].threadid, NULL); Gif_DeleteArray(ctx); } #else nthreads = 1; #endif if (nthreads <= 1) { scale_context sctx; sctx_init(&sctx, gfs, nw, nh); sctx.scale_colors = scale_colors; for (sctx.imageno = 0; sctx.imageno < gfs->nimages; ++sctx.imageno) { sctx.gfi = gfs->images[sctx.imageno]; scale_image(&sctx, method); } sctx_cleanup(&sctx); } gfs->screen_width = nw; gfs->screen_height = nh; } gifsicle-1.96/test/000077500000000000000000000000001475770763400142415ustar00rootroot00000000000000gifsicle-1.96/test/001-transexpand.testie000066400000000000000000000061161475770763400203110ustar00rootroot00000000000000%script for i in 0 1 2 3; do gifsicle -O$i x.gif > y.gif gifdiff x.gif y.gif done %file -e x.gif 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 uidghuXc1idOy6MskEDXLygiDIiLgVIJSgAAOw== gifsicle-1.96/test/002-cropresize.testie000066400000000000000000001134031475770763400201460ustar00rootroot00000000000000%script gifsicle -O2 --crop 25,0+36x36 --resize 100x100 x.gif > y.gif %file -e x.gif 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 gifsicle-1.96/test/003-bgopt.testie000066400000000000000000007272531475770763400171130ustar00rootroot00000000000000%script for i in 0 1 2 3; do gifsicle -O$i x.gif > y.gif gifdiff x.gif y.gif done %file -e x.gif R0lGODlhLAHIAPcAAAAAAAsBAQQJAQsKAQEBCwsDCwoKCgMKDBMBABsCABUIBgwSAxcWCwIBEwsB EwYGGBIGFRMTExsbGxUbGQ4QGiMAACIKACoHAjMMACoSATsdADYYACEiGzYiABgZIxQPLSEcKCMj IysrKzMzMzs7OxwhHkYZAEspAFMrAFk4Ak44CHA5AFxBCmVDBHZVDHtdKUNDQ0tLS1RUVFpaWmNj Y2trbGprZXJycnx8fHRxeGtoc4lUCZNtGqhxF45aJYhoI5RwLK19O+l+MJZ5RrWMMJyHHsqcBtyb AtmZCcqYGNmZF+WVAeyUAuSdAuydA+WcDO2cC+eXDPKdA/OdC/eYBuScEuyeE+WcHeqcG+mWFfSc EvecGO+PF9WiAtyjAt2kDN2qB9ykG9imF9OsFeSkAuyjAuOqAeuqAuSkDeyjC+qrC/GiBPulBPur A/GiCvOsC/inCOq1BPe0AuWkFOyiE+OjG+ujG+WsHOirFvSiE/OrFPSjGvmnGeqzGvW0FMqcJtyd IdWbKMubOtibN8SNMOiaJvWbJvKZMdimJ9enOM2rLuamJ/WoKOi2J/m1JOSrO+ilN/ekNfy3Neaz NvvHLLuGSbGTSryYaNaZRdeeWMqSTd6sTNqlSNqzR8+jVNipW9u0V8WkSOOoSOq2SP21ReSmUui3 W/SvUdqyZei6Y/O0bP+dUvrFRfvIVP7WVvPNR+fHav3IZ//YZ/3Mef/ZdufLef/pcf/qX4ODg4yM jJOTk5ycnJ+WkY6Nk6SkpK2trb+3rrS0tLu7u724sKiYgdm1mNS/sbjDuurIhPvEhv7Yh/rMk/3a lP/niv/mmf/3l//3itDJtvjOrP7oqf/1p//quP/3t8S/xrzFyb7J3cTExM3NzcjDxcrM1c3U3NPT 09vb29fX1tDMzP/uxv/8w//7y//3yP/s2f/90//23f/93P3z0+rv3OXl5ezs7Orq4P/05P/85P79 7Pf66//t9+v8/PT09Pz19Pb78/799P31/vX8/v7+/vX1/fTy6uHY3SH+A0c0NAAh+QQIKAAAACH/ C05FVFNDQVBFMi4wAwEAAAAsAAAAACwBqAAACP8AAQgcSLCgwYMIEypcyLChw4cQI0qcSLGixYsY M2rcyLGjx48gQ4ocSbKkyZMoU6pcybKly5cwY8qcSbOmzZs4c+rcybOnz59AgwodSrSo0aNIkypd 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 TDMjRzUpWDgnQy02SDg2Vzo2VyksZDskZD0saj0mYzwyczkrXB0VVkQcaEUbdE4aWkooSEQ6WEY4 V00xbEEsaEQnckIsdkcnaEY2d0k1cFUweGUwfWAaKx1KLh9QHSxILCBMJypFNilHNjVHLCVTMiNT NyhaKDdYMjZROy1jOjRmMTltHxVDSDdORTRrSjptRTZnTDxySDp2Uj53RjRnbTpJOENXNUhuS0pG Uk9KVUxFVlNKXl5eWVdVT0pXZlpFaVlHdlhGZl1Xak9JXmBcZWNadGdUTEJ3VER0W0t7Vkd5UVBt ZVR0aGdmdnRvXGNtPkE6mTMd0ToVjFIcsXMah0oqhUw0iVM4mFc4kFUqqFw3s141rlYrkG4vq2I4 s2I0vGM0uGU6q3MqlmUcz1EvyWkt1G4t2HUpxGU1y2Y0y2ozxmk41Gs02XM4ynQs6Hc363sx7VMu ildFjXJKsWtJiHZvpVxbz3RH9WxS/Hpo+XVs5lxQkjxBfoJ2sooysJg4yJUy6oIo06k7m4VGnIZJ mYZKmIlUsJJKh4V5lop4p5R5tJR3uqNU0q5Oz5d1/Yd585Bw96V62qJQ889Q991hOlGIP2SVV0uE T1COZ1iGaVqMUmiSc2OLcm2SV1ukV2ysa3KzcHvCQz6ChHiPm3mcdYiwcoKadonJmpSFkY6OpZiI rZiOp6WWraSRkpOura2tp6emsq+xoZ2r+Y6E0aaI+62K2ame5c6riJXLlajOq7TQoLPjucnltsXY z8/Sy9bq2ef06/b8+vv29fz8/Pz89vj27+7v89zVwrjECP8A3a0b2G6gwYMI1xUrJkycMGED3ZGL uGzdMHcY3flbR+5ixmXkwgWDOBEiwonrxA1UiVKcNm2/voH7Ji6cNlbAxOnUCQ5dvZ9AgwodWi8e uF/aCtGiVogaNVZOf/2i9quQNnBWX2rVRg1cV2rbyolNJ3arVmpm06p9iVatV7PbwJVLR5fuXLro 0MWLh+7fv7p1twnWNrdvvH9686JLtxjwtnSPH9utC+4xOHAvmyLFejkz11+XO7O6XCj0ZZmXxV1e J+lTa1CbJGHaFPvSp9u3OXHKpMiIE0WaNA0jJoxSLWLDbC0itqw5sVrBbSmbrmsKIyNGIk2qxH2S pEeRJE3/Ci/euyRJkR45eeDgQQoePZpA6fEgCJRZUJ7wYJVO3Llz4/x3jjnmCEjggQT+Z84vH4hw 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 AC5JRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAA AAAAAAAAAAAAAAAAZGVzYwAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElF QzYxOTY2LTIuMQAAAAAAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBpbiBJRUM2 MTk2Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHZpZXcAAAAAABOk/gAUXy4AEM8UAAPt zAAEEwsAA1yeAAAAAVhZWiD/AAAAAABMCVYAUAAAAFcf521lYXMAAAAAAAAAAQAAAAAAAAAAAAAA AAAAAAAAAAKPAAAAAnNpZyAAAAAAQ1JUIGN1cnYAAAAAAAAEAAAAAAUACgAPABQAGQAeACMAKAAt ADIANwA7AEAARQBKAE8AVABZAF4AYwBoAG0AcgB3AHwAgQCGAIsAkACVAJoAnwCkAKkArgCyALcA vADBAMYAywDQANUA2wDgAOUA6wDwAPYA+wEBAQcBDQETARkBHwElASsBMgE4AT4BRQFMAVIBWQFg AWcBbgF1AXwBgwGLAZIBmgGhAakBsQG5AcEByQHRAdkB4QHpAfIB+gIDAgwC/xQCHQImAi8COAJB AksCVAJdAmcCcQJ6AoQCjgKYAqICrAK2AsECywLVAuAC6wL1AwADCwMWAyEDLQM4A0MDTwNaA2YD cgN+A4oDlgOiA64DugPHA9MD4APsA/kEBgQTBCAELQQ7BEgEVQRjBHEEfgSMBJoEqAS2BMQE0wTh BPAE/gUNBRwFKwU6BUkFWAVnBXcFhgWWBaYFtQXFBdUF5QX2BgYGFgYnBjcGSAZZBmoGewaMBp0G rwbABtEG4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfSB+UH+AgLCB8IMghGCFoIbgiCCJYIqgi+ CNII5wj7CRAJJQk6CU8JZP8JeQmPCaQJugnPCeUJ+woRCicKPQpUCmoKgQqYCq4KxQrcCvMLCwsi CzkLUQtpC4ALmAuwC8gL4Qv5DBIMKgxDDFwMdQyODKcMwAzZDPMNDQ0mDUANWg10DY4NqQ3DDd4N +A4TDi4OSQ5kDn8Omw62DtIO7g8JDyUPQQ9eD3oPlg+zD88P7BAJECYQQxBhEH4QmxC5ENcQ9RET ETERTxFtEYwRqhHJEegSBxImEkUSZBKEEqMSwxLjEwMTIxNDE2MTgxOkE8UT5RQGFCcUSRRqFIsU rRTOFPAVEhU0FVYVeBWbFb0V4BYDFiYWSRZsFo8WshbWFvoXHRdBF2UXiRf/rhfSF/cYGxhAGGUY ihivGNUY+hkgGUUZaxmRGbcZ3RoEGioaURp3Gp4axRrsGxQbOxtjG4obshvaHAIcKhxSHHscoxzM HPUdHh1HHXAdmR3DHeweFh5AHmoelB6+HukfEx8+H2kflB+/H+ogFSBBIGwgmCDEIPAhHCFIIXUh oSHOIfsiJyJVIoIiryLdIwojOCNmI5QjwiPwJB8kTSR8JKsk2iUJJTglaCWXJccl9yYnJlcmhya3 JugnGCdJJ3onqyfcKA0oPyhxKKIo1CkGKTgpaymdKdAqAio1KmgqmyrPKwIrNitpK50r0SwFLDks biyiLNctDC1BLXYtqy3h/y4WLkwugi63Lu4vJC9aL5Evxy/+MDUwbDCkMNsxEjFKMYIxujHyMioy YzKbMtQzDTNGM38zuDPxNCs0ZTSeNNg1EzVNNYc1wjX9Njc2cjauNuk3JDdgN5w31zgUOFA4jDjI OQU5Qjl/Obw5+To2OnQ6sjrvOy07azuqO+g8JzxlPKQ84z0iPWE9oT3gPiA+YD6gPuA/IT9hP6I/ 4kAjQGRApkDnQSlBakGsQe5CMEJyQrVC90M6Q31DwEQDREdEikTORRJFVUWaRd5GIkZnRqtG8Ec1 R3tHwEgFSEtIkUjXSR1JY0mpSfBKN0p9SsRLDEtTS5pL4kwqTHJMuk0CTf9KTZNN3E4lTm5Ot08A T0lPk0/dUCdQcVC7UQZRUFGbUeZSMVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYPVlxWqVb3V0RX klfgWC9YfVjLWRpZaVm4WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1fD19hX7NgBWBX YKpg/GFPYaJh9WJJYpxi8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn6Wg/aJZo7GlDaZpp 8WpIap9q92tPa6dr/2xXbK9tCG1gbbluEm5rbsRvHm94b9FwK3CGcOBxOnGVcfByS3KmcwFzXXO4 dBR0cHTMdSh1hXXhdj7/dpt2+HdWd7N4EXhueMx5KnmJeed6RnqlewR7Y3vCfCF8gXzhfUF9oX4B fmJ+wn8jf4R/5YBHgKiBCoFrgc2CMIKSgvSDV4O6hB2EgITjhUeFq4YOhnKG14c7h5+IBIhpiM6J M4mZif6KZIrKizCLlov8jGOMyo0xjZiN/45mjs6PNo+ekAaQbpDWkT+RqJIRknqS45NNk7aUIJSK lPSVX5XJljSWn5cKl3WX4JhMmLiZJJmQmfyaaJrVm0Kbr5wcnImc951kndKeQJ6unx2fi5/6oGmg 2KFHobaiJqKWowajdqPmpFakx6U4pammGqaLpv2nbqfgqFKoxKk3qamq/xyqj6sCq3Wr6axcrNCt RK24ri2uoa8Wr4uwALB1sOqxYLHWskuywrM4s660JbSctRO1irYBtnm28Ldot+C4WbjRuUq5wro7 urW7LrunvCG8m70VvY++Cr6Evv+/er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7FS8XIxkbGw8dBx7/I Pci8yTrJuco4yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+0j/SwdNE08bUSdTL1U7V0dZV 1tjXXNfg2GTY6Nls2fHadtr724DcBdyK3RDdlt4c3qLfKd+v4DbgveFE4cziU+Lb42Pj6+Rz5Pzl hOYN5pbnH+ep6DLovFTpRunQ6lvq5etw6/vshu0R7ZzuKO6070DvzPBY8OXxcvH/8ozzGfOn9DT0 wvVQ9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH/Jj9Kf26/kv+3P9t//8ALAAAAAAsAcgAhxwdHBka Fx0WCywcGB4gHhciHSIiHjcmGxwdIxwZMyMcKR4hIRwkKxkpNCIjIigpKSYrOTg5OTAxMkccGVQe G0oiHFUiHU41HkslI1gnJFE4M2krJ3QsJnMsKXksKWk0KnQzK203NWEiHjtBPE1FOm1MOHNjPS00 RDM6Szc5RzU1TjEsWkc4S18ybThFTTJZYUdGR1JQU21UTnppR3JoVldaaFVUcGhUcVpmcGdnaWdn dnh4eXVublJoXIlzVYt2bZBiYoFMPnCJeFOLdpaHXI6EWJmHZZeJeI2Id6iSdbKldsO4fnMvkHwY ungYrngorGIomFdMmVtVmFdOjWZZmHhXl3FUi1RykHh4h3VpmHl1mG1qjVVKplZPqWxUq2lmqHl2 p3d1t3Burlp0rHwOyn0Ux30Q0Xogw3Z1xnBUyDpjhYMbupkauIQbqqIbu4gmt5c5uo8tsaQvuIQv mIdQkoV6mopyj5hHt6VKvIp3qal1poMbxIMXyYQUz4slx5Qry5o4yJs105s61Jk31ZUs0qQ006cf 0ptGxaVJxatVx7NYzrtn1bNrzJBv0sd32sNs2Mp548535MRd18B/vVaPknCPkU+umGupmFeJqFKM rnCKq064pXGwqjzFpT/EnkrEqWbKtmzJtUnDnXWN5nWL0kO2n4eIiJeYmJGNkKeYh66Wj5OlmLim ibeol6ynlpmXp5GMq6aZt6mTr5KpsKapqKqotre6ubOvr8i7iMaqmMq3mNa7ntm7ndS2m8eokMm4 p9W7o9m8ota7qtm7q9W2pta8s8qyrsudmL3CvK3DsIvEtcbFutTLtdbKuNLHsM7Bk7KTypaxxLeq yLq2yLG0zZ+X4suH29OW3cyQ2sO2y8y83Ma41cyx2NSM49SH59ST49Wd5Nia58yN5Nah5dmh5tij 6c694sm46NS65cuq5uG25LzFxazH2LbJ26rFzrPN47vM47XS5bbU6bnU6rnT5q7O453DycXHxsjE 2szS09PO0czD4tLD5NTE6MbU49vl4gj/ALFduwYNWh49BcWJu4YNGqM7TppI3OPnD5w7bTK2gaNI zpw1b+IAAgQHUSJJkbp9+zbwGrhx5MqlS3eu5rl55+DZPFeNlCZOm0ptAgWK0yV79erJczfLkqVO UDuVKkU0FNGrWKMhrffuXbx58+LFUypPHj168uKhHSuvHjtNYLRsgSXNHD98/PLq5WdOm99tfvfm tctPG7dYWbRo0kSNndizZsWWTfuYXjx57WC9WlWJc+dQoEFXBSWqdCinQyolW3fvHrLXy+4tW7Yu WaVKdGnW27lzJrlw4cqR+wbu2rdu1h4lwgMHzp8/a9a0iYPnzpw3bZzIuRMHzpsyEs/4/1EESeG3 dAX1NCoITSA2bNbey4eWbr44R4nmPIlIhiKgN3jgMQcT03GkCBwZxfHGH4DIAQceiSziiDfdDPQS OeOMcw1NvOFUEzV5aLLJJ0JxUtQsSHnFziyUbLJJVFJZgtWMRXESTTTscKXWPGmRdZZlZ4kllluz wKVFLNygg5dg5uxjTl9+EaaNXoQVpg00WmhhiibR1CMkZJIJKRmP8kQzyzrr0ILKKqtcokxoVl1l ySWXWDKEm8kkg4xre/ZJCzLr3HaJFvSdk1RN8hhak2/k/Fbce8ktd0dzccTRxhtPvGGSHJVmJAce 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 ////////////////////////A+gAAAAA/////////////////////////////wPoAAAAAP////// //////////////////////8D6AAAAAD/////////////////////////////A+gAADhCSU0EAAAA AAAAAgAAOEJJTQQCAAAAAAAGAAAAAAAAOEJJTQQwAAAAAAAD/wEBAQA4QklNBC0AAAAAAAYAAQAA AAg4QklNBAgAAAAAABAAAAABAAACQAAAAkAAAAAAOEJJTQQeAAAAAAAEAAAAADhCSU0EGgAAAAAD TwAAAAYAAAAAAAAAAAAABLAAAAeAAAAADQBUAHIAaQBvADEAOQAyADAAeAAxADIAMAAwAAAAAQAA AAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAeAAAAEsAAAAAAAAAAAAAAAAAAAAAABAAAAAAAA AAAAAAAAAAAAAAAAABAAAAABAAAAAAAAbnVsbAAAAAIAAAAGYm91bmRzT2JqYwAAAAEAAAAAAABS Y3QxAAAABP8AAAAAVG9wIGxvbmcAAAAAAAAAAExlZnRsb25nAAAAAAAAAABCdG9tbG9uZwAABLAA AAAAUmdodGxvbmcAAAeAAAAABnNsaWNlc1ZsTHMAAAABT2JqYwAAAAEAAAAAAAVzbGljZQAAABIA AAAHc2xpY2VJRGxvbmcAAAAAAAAAB2dyb3VwSURsb25nAAAAAAAAAAZvcmlnaW5lbnVtAAAADEVT bGljZU9yaWdpbgAAAA1hdXRvR2VuZXJhdGVkAAAAAFR5cGVlbnVtAAAACkVTbGljZVR5cGUAAAAA SW1nIAAAAAZib3VuZHNPYmpjAAAAAQAAAAAAAFJjdDEAAAD/BAAAAABUb3AgbG9uZwAAAAAAAAAA TGVmdGxvbmcAAAAAAAAAAEJ0b21sb25nAAAEsAAAAABSZ2h0bG9uZwAAB4AAAAADdXJsVEVYVAAA AAEAAAAAAABudWxsVEVYVAAAAAEAAAAAAABNc2dlVEVYVAAAAAEAAAAAAAZhbHRUYWdURVhUAAAA AQAAAAAADmNlbGxUZXh0SXNIVE1MYm9vbAEAAAAIY2VsbFRleHRURVhUAAAAAQAAAAAACWhvcnpB bGlnbmVudW0AAAAPRVNsaWNlSG9yekFsaWduAAAAB2RlZmF1bHQAAAAJdmVydEFsaWduZW51bQAA AA9FU2xp/2NlVmVydEFsaWduAAAAB2RlZmF1bHQAAAALYmdDb2xvclR5cGVlbnVtAAAAEUVTbGlj ZUJHQ29sb3JUeXBlAAAAAE5vbmUAAAAJdG9wT3V0c2V0bG9uZwAAAAAAAAAKbGVmdE91dHNldGxv bmcAAAAAAAAADGJvdHRvbU91dHNldGxvbmcAAAAAAAAAC3JpZ2h0T3V0c2V0bG9uZwAAAAAAOEJJ TQQoAAAAAAAMAAAAAT/wAAAAAAAAOEJJTQQUAAAAAAAEAAAACDhCSU0EDAAAAAAbTgAAAAEAAACg AAAAZAAAAeAAALuAAAAbMgAYAAH/2P/gABBKRklGAAECAP8ASABIAAD/7QAMQWRvYmVfQ00AAf/u AA5BZG9iZQBkgAAAAAH/2wCEAAwICAgJCAwJCQwRCwoLERUPDAwPFRgTExUTExgRDAwMDAwMEQwM DAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwBDQsLDQ4NEA4OEBQODg4UFA4ODg4UEQwMDAwMEREM DAwMDAwRDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDP/AABEIAGQAoAMBIgACEQEDEQH/3QAE AAr/xAE/AAABBQEBAQEBAQAAAAAAAAADAAECBAUGBwgJCgsBAAEFAQEBAQEBAAAAAAAAAAEAAgME BQYHCAn/CgsQAAEEAQMCBAIFBwYIBQMMMwEAAhEDBCESMQVBUWETInGBMgYUkaGxQiMkFVLBYjM0 coLRQwclklPw4fFjczUWorKDJkSTVGRFwqN0NhfSVeJl8rOEw9N14/NGJ5SkhbSVxNTk9KW1xdXl 9VZmdoaWprbG1ub2N0dXZ3eHl6e3x9fn9xEAAgIBAgQEAwQFBgcHBgU1AQACEQMhMRIEQVFhcSIT BTKBkRShsUIjwVLR8DMkYuFygpJDUxVjczTxJQYWorKDByY1wtJEk1SjF2RFVTZ0ZeLys4TD03Xj 80aUpIW0lcTU5PSltcXV5fVWZnaGlqa2xtbm9ic3R1dn/3eHl6e3x//aAAwDAQACEQMRAD8A8qUm MLvIeKs9L6XndXza8DAr9XJtnYyQ0GBP07C1jf7S1b/qd9a8Z3pv6PmEt0/R1OtH+fR6jElOMGMb wPmVKAeQtrG+pX1uyXba+kZTT/wrPSH+dkGpq1af8WfWKWMv67mYXQ8dxhzsm5rn/wDW2sd6D3fy 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 AAAAAAAAAAAAAABkZXNjAAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRpb24gaW4gSUVD NjE5NjYtMi4xAAAAAAAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElFQzYx OTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdmlldwAAAAAAE6T+ABRfLgAQzxQAA+3M AAQTCwADXJ4AAAABWFlaIP8AAAAAAEwJVgBQAAAAVx/nbWVhcwAAAAAAAAABAAAAAAAAAAAAAAAA AAAAAAAAAo8AAAACc2lnIAAAAABDUlQgY3VydgAAAAAAAAQAAAAABQAKAA8AFAAZAB4AIwAoAC0A MgA3ADsAQABFAEoATwBUAFkAXgBjAGgAbQByAHcAfACBAIYAiwCQAJUAmgCfAKQAqQCuALIAtwC8 AMEAxgDLANAA1QDbAOAA5QDrAPAA9gD7AQEBBwENARMBGQEfASUBKwEyATgBPgFFAUwBUgFZAWAB ZwFuAXUBfAGDAYsBkgGaAaEBqQGxAbkBwQHJAdEB2QHhAekB8gH6AgMCDAL/FAIdAiYCLwI4AkEC SwJUAl0CZwJxAnoChAKOApgCogKsArYCwQLLAtUC4ALrAvUDAAMLAxYDIQMtAzgDQwNPA1oDZgNy A34DigOWA6IDrgO6A8cD0wPgA+wD+QQGBBMEIAQtBDsESARVBGMEcQR+BIwEmgSoBLYExATTBOEE 8AT+BQ0FHAUrBToFSQVYBWcFdwWGBZYFpgW1BcUF1QXlBfYGBgYWBicGNwZIBlkGagZ7BowGnQav BsAG0QbjBvUHBwcZBysHPQdPB2EHdAeGB5kHrAe/B9IH5Qf4CAsIHwgyCEYIWghuCIIIlgiqCL4I 0gjnCPsJEAklCToJTwlk/wl5CY8JpAm6Cc8J5Qn7ChEKJwo9ClQKagqBCpgKrgrFCtwK8wsLCyIL OQtRC2kLgAuYC7ALyAvhC/kMEgwqDEMMXAx1DI4MpwzADNkM8w0NDSYNQA1aDXQNjg2pDcMN3g34 DhMOLg5JDmQOfw6bDrYO0g7uDwkPJQ9BD14Peg+WD7MPzw/sEAkQJhBDEGEQfhCbELkQ1xD1ERMR MRFPEW0RjBGqEckR6BIHEiYSRRJkEoQSoxLDEuMTAxMjE0MTYxODE6QTxRPlFAYUJxRJFGoUixSt FM4U8BUSFTQVVhV4FZsVvRXgFgMWJhZJFmwWjxayFtYW+hcdF0EXZReJF/+uF9IX9xgbGEAYZRiK GK8Y1Rj6GSAZRRlrGZEZtxndGgQaKhpRGncanhrFGuwbFBs7G2MbihuyG9ocAhwqHFIcexyjHMwc 9R0eHUcdcB2ZHcMd7B4WHkAeah6UHr4e6R8THz4faR+UH78f6iAVIEEgbCCYIMQg8CEcIUghdSGh Ic4h+yInIlUigiKvIt0jCiM4I2YjlCPCI/AkHyRNJHwkqyTaJQklOCVoJZclxyX3JicmVyaHJrcm 6CcYJ0kneierJ9woDSg/KHEooijUKQYpOClrKZ0p0CoCKjUqaCqbKs8rAis2K2krnSvRLAUsOSxu LKIs1y0MLUEtdi2rLeH/LhYuTC6CLrcu7i8kL1ovkS/HL/4wNTBsMKQw2zESMUoxgjG6MfIyKjJj Mpsy1DMNM0YzfzO4M/E0KzRlNJ402DUTNU01hzXCNf02NzZyNq426TckN2A3nDfXOBQ4UDiMOMg5 BTlCOX85vDn5OjY6dDqyOu87LTtrO6o76DwnPGU8pDzjPSI9YT2hPeA+ID5gPqA+4D8hP2E/oj/i QCNAZECmQOdBKUFqQaxB7kIwQnJCtUL3QzpDfUPARANER0SKRM5FEkVVRZpF3kYiRmdGq0bwRzVH e0fASAVIS0iRSNdJHUljSalJ8Eo3Sn1KxEsMS1NLmkviTCpMcky6TQJN/0pNk03cTiVObk63TwBP SU+TT91QJ1BxULtRBlFQUZtR5lIxUnxSx1MTU19TqlP2VEJUj1TbVShVdVXCVg9WXFapVvdXRFeS V+BYL1h9WMtZGllpWbhaB1pWWqZa9VtFW5Vb5Vw1XIZc1l0nXXhdyV4aXmxevV8PX2Ffs2AFYFdg qmD8YU9homH1YklinGLwY0Njl2PrZEBklGTpZT1lkmXnZj1mkmboZz1nk2fpaD9olmjsaUNpmmnx akhqn2r3a09rp2v/bFdsr20IbWBtuW4SbmtuxG8eb3hv0XArcIZw4HE6cZVx8HJLcqZzAXNdc7h0 FHRwdMx1KHWFdeF2Pv92m3b4d1Z3s3gReG54zHkqeYl553pGeqV7BHtje8J8IXyBfOF9QX2hfgF+ Yn7CfyN/hH/lgEeAqIEKgWuBzYIwgpKC9INXg7qEHYSAhOOFR4Wrhg6GcobXhzuHn4gEiGmIzokz iZmJ/opkisqLMIuWi/yMY4zKjTGNmI3/jmaOzo82j56QBpBukNaRP5GokhGSepLjk02TtpQglIqU 9JVflcmWNJaflwqXdZfgmEyYuJkkmZCZ/JpomtWbQpuvnByciZz3nWSd0p5Anq6fHZ+Ln/qgaaDY oUehtqImopajBqN2o+akVqTHpTilqaYapoum/adup+CoUqjEqTepqar/HKqPqwKrdavprFys0K1E rbiuLa6hrxavi7AAsHWw6rFgsdayS7LCszizrrQltJy1E7WKtgG2ebbwt2i34LhZuNG5SrnCuju6 tbsuu6e8IbybvRW9j74KvoS+/796v/XAcMDswWfB48JfwtvDWMPUxFHEzsVLxcjGRsbDx0HHv8g9 yLzJOsm5yjjKt8s2y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW 2Ndc1+DYZNjo2WzZ8dp22vvbgNwF3IrdEN2W3hzeot8p36/gNuC94UThzOJT4tvjY+Pr5HPk/OWE 5g3mlucf56noMui8VOlG6dDqW+rl63Dr++yG7RHtnO4o7rTvQO/M8Fjw5fFy8f/yjPMZ86f0NPTC 9VD13vZt9vv3ivgZ+Kj5OPnH+lf65/t3/Af8mP0p/br+S/7c/23//wAsAAAAACwBqgCHBQMCCQYE DAoFCwULDQoMBgUJEwsFEg0LGAwHFBENGxQMGxYLDQsSCggSEg0TFA4ZDhESDBQZFBITGhUTHRoU FhIbGRQdHBsbFhkYDhENIxcNIhwUIh0aKRwWLhcPJSEbKyMcKCMYMiQdNicdPzEfGRUhGxgkExQn Ix0lJBwzGigsJSIjKiQkLCgpMyYhNCojOywkNi0rPDIsNjIrLSQ8KyczMS4yMiw8NTMzOzg4KTQ3 HCAdQi4nRDMrSjUtRTYqSzozRjk1Uzs0VDw5VDkqTjAeSEM6V0Q7UUw3bU02cWgzOzdGMS5DGDVI Qj1HRTxUNUlPN1lkN2d0RUNFTEpFSUZKTUpNR0VHUU5MVkpEVFJNWVVJTURdTEhTUU5RUkpcVVNU WVVVXFpUWVZaXVpcVlVZSlRUZlhKYV1dZltVcFhJaGVadWhYdG1UTkRiT0NoUUVkVEtlW1ZnV1Zt Yl5hYlxtZFt2UWpwZWJkaWVla2ZpbWpraWhncW1udWpld3Npa2d3cm5xcWt8dnNzeXZ1e3h3enZ5 fXt7dXR6a3N1WmNcOUA1iV44jmxSqHdVh3hogX5+hnt1lHppmG9YsqM9los5sIpZs6lIg4F+ioZ4 l4l3kIhxsotwmZJayJZ0zq5o3tBeT3qFdGuGe3WJdW6IPXKEgX+AgXqNhHuWTYyaVIuYVYyUa42T WJWkbJqlcqy3dLXFdbjGdrfKeLfJdrjJe7nHcLbGfMLPhYKCiYWFiomJk4yJmZWIi4WamJaXj5CV ppeHpZuYs5mJqqeXuKmXtbGcubScrqOMk4ujm5Wplo+ppJy0oZqqhq+2qKWmuramqqS5t7Kxn6Cd 2KeGxKmX0a6U5q6J5rKN6bKO6bOI5bOR6rSR67qY87uX7bGOx7enzbuv6b2i872jxp2Eysa31si4 0cu06siv88Obg7vHlrvHtrPEmMLM2tjK29jS0cvH5dLE69TG7NTJ5trJ6dzT8NrN5OLG5ePK6eTK 6ujW8+fX8u7X7erp3uDcwb7BaoJ/CP8AAQQAMKCggIIFCShkQMCBAwMGFCyYOFEDhQkTNCjQsMGC Bw0fNXTwMFKECx4ohQghQkRIEiJHhsiUGRMJzCNHjPz40UPIkJhJZI7w8AOmEZBDiCRNqiHEhgUc N0gNQUHqAqkbqmKlcIGCV68Ur05dkIHEIp49eoyUgeRHiLRwffRYICJEiBEfdmSAIOHBAQGABRgQ DNhABgEZMjRg8OBBhQoRHjt+XKJxZcuYG2vWzJDBYYgZDCQ4jDiB6AQJQFxAwcUNl9duYrtu/fr1 kycpbr9xPZu3bN67Yctu/Zs3lycqLuRdoDUrWYTQCzJgIL3hgQoOEni1uF0kyQ7gPYj/MAmDh48h KmVmmUkzJs4kQYPW/EFihIiQCzrcpDlCww+cR2ggAnvoceRUVWJhhdUOUnFwEQcQQpjVVwvsYJdd T5GwE1wdaEBCFm71sJMPPwRBggYgjGBXCx9gMEEFDyQggGeIZYDaAaaZBsEDEkRgwY8/UvbYYyeU YGSRJZzwgJFGPmYBjA549leNMuKI2mkGUKBaDbS5sdtvX4KZwhvCERdmbFycWRyYxXFBQwlOMcfg BjtMVFAAAyiUUJ4EUMcAAgdM4MABFGhg6AYdgGDSST6Y59NMOMkU3xBBUfoTTkgIgdIII4CnQVgI fLoAAh74ANRP/Zl6BBICUurTEUJ4/xDCC04puEGtO3xw6wYc2HqBgxRQREGuds0QggIa/uDDsvfJ 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 EA8RDhETDBUaExMTFBcZFRobGxscGhcVJRsYLBkTHCcNHScUJiMcNyQdFx4iCRkjHSMjGCUqGSsz GzA3IyQkKiYjKysrJykoNSsmNzItMzMzPTo0Ozs7OzY0KTQzRjUrRTw2UDcrSzIlOk0wS0Q8RUM8 V0U5RVg6Zko5dVA5S2Y9JjtFPj5AGzZCMUpTLFlpNF5uOVpmNF5wN2R0NmBuQ0NDTEpES0tLR0hG Uk1EV0tFVVNLW1RLWFVJU1NTXFxcWllVTVJQZ1ZKd1lGY11TalxValNIVm9Ia2RaaGVZd2dbdWpU R2p2TG12Y2Rja2tramlmd2xjeXVndHR0fHx8eHh2bm9wTF1jQT5DhVo9jmA/hFtCh15GimNIiGtX jHRdhnFZmnRblGtRpnhYh3hplXtoh31zjXdrp31jZ4BbioR0iIZ2k4x7l4Z4lpJ9kYJst4lprYty q49Yx5h2zJdx16N9zaF846t9TXqJfX6BPW+AgH+BV4iYUISUXJapZZmpaJ6xaqa4c6u7aYmTbrfL crbMcrnPeLfIc7rRer7SdbjRb7PIe8LUfMTad8LWc8DPfsbgg4ODi4uMh4eIm5aEk5OTm5ubl5eX lI6HpZmIrpmMqKOMq6eTtK6Vta+YuqeXtrCXtrCYu7WcvrmeubOXtqWMo6OjrKysp6eovrqhtrKn s7Ozu7y7t7e4sa6vn5+gxKubwbqexrSb1qmI46yF7rOG7bSK5rOL8rWG87aJ9bqM+ryN9riH67ya +L2Swr2jyLalz7mr1aqOv8G+7cGf/sOU/caZ/sKQyMKo2se00caz78Kh5sSp9sin58y57dK96tm9 5dW58NS//dW0+dCu8uC9vb7Bgsbajczhw8TDzMzMx8nI09PT3Nzc19jX0NDO7tTB7NvD8dTB9NrE +97K+NvG6tzT/uPM9uXJ/uXR/OrW//TY6ODQ3d7g5eXl6+vr6Ojn8/Pz/v7+9/j48fDu3+DhCP8A hQAYSLCgwYMIEw4UwLChw4YIEBgQQICAgAEKM2rcyLGjx48gQ4oUAKmkyZORagDAWDDAQAY7Tsqc SbMmJEkmH0l6BOnSDpEABAL1OIChRYkWjxqoSAABw4gGnLIcSrWq1atYMxq6pDPnTUmTfgJwSRCD FZto06KVFOkBUIFksxIcQEABw6gR897N6zQqRQR1BcSVS7iwYbkud1xCG85KXAxX1EpOi1OSI0iP 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 gifsicle-1.96/test/004-careful.testie000066400000000000000000001176251475770763400174160ustar00rootroot00000000000000%script for i in 0 1 2 3; do gifsicle -O$i x.gif > y.gif gifdiff x.gif y.gif gifsicle -O$i --careful x.gif > y.gif gifdiff x.gif y.gif done %file -e x.gif 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 VquCPNrD/RAIACH5BAQAAP8ALAAAAABkAFAAAAf/gHgyBQWDhDJ/hAV/g4gyg4yFiYyRjYSMjzJp koaUlouHj56TiY6Sh4mXiKCXipEhNZWFs5aNj7NporWFmpk1BTW3u7aNmJvDspnJnLSVlSWqp6mG vGnTnJOrtLy7i8WEm4cF4c6Ons3andTilg3mwbXCMr/EwtjxmfbD8pm0+uj+8g3bJiNOq1OqIpFj VWwVJVahPl364wmSsm/MvEkzZW5dKlTuAjIjRlBfOVy5/uTqx1GGnGoERxI82RGgIxLrbkW7lQyT KUWK+uXSuHLjrV+oPl0zp7HWyaQFGrx0lE/kQHoxH9FzGXCewGDBItq8l9UqwRLCSjlkOonhxoPh /woFyzZNIMtpeB1C3EnxYSRQkUjI8ckNV1mSHAs/ovMP0cqqAyNnTStZhh2ESpUCZmVt8zZv89qK Bu3U21+gCU1D/Hsa1YKituBBnpyYpaQ0c5E9SvN4duWsFffJWNC3JkZqa30Wl6YRKV52oREHZd0J lTjT6g6GqO17ZjHY47KmrF1Nl7FMxw4H7Dns8ual3oOSJadO5VBXxDC9362z7az98jUVjR2RaaJS Y4qhR5k8dHBTyTy80JOML/xM6Bg/WbnnH2nLSDTIMaI05cl4/41jETUf1YWYX9ehhghFrHhQIDz+ gFdPZTIwlh9vVFkzEGxe2QhcZCGNdRxNih3UUv9ndR2jXHaZleeRbdjJ54GP4jkJ5G4RVjhMWLFt Q2ND3S3Go1CcmJSJB0CJVoqLdP2mWQGDwciLgB09qA1gtgiZnIBx4BiLcffog6AmgyqmZ5qycXSO LXJA1iEjbIqYzqHZGChdOoV0dpGLreiXl5LZwFRdan8QmGZiY3oH4Y3bPNbgI4MNM9SKupGVj42V aPjTOU5+cw52hNlqTSwUvagIfaKk4oxqqMKaVKoF4toVeX1+1cummgKDzqP36Hleq5aFuo4qOamY omqZUhVpN8Hd+VGJlk6XznmdvCYTdq92hWVD3+ZTgxy/+PnqrfFVO9KD72YQl515mScOkyIm+2z/ JC/huaou5pJqnF95UgUGHY8uw6Msfsq0j4TZ0rrtKIQ6am1tC4QYDbsUy8uuKzz7x2Oo7Ji4GlMZ IdnQsH8ssJWg622q3mJG8sdxZDQVq+YjRcpMypPMJeRo0I7w1lqaPiJdX5VQDc3Ma+XYNQuNLHf4 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 05K4Cp7CsAqcYFchEAgAOw== gifsicle-1.96/test/005-resize.testie000066400000000000000000000106341475770763400172670ustar00rootroot00000000000000%script gifsicle --resize 30x30 x.gif > y.gif %file -e x.gif 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 gifsicle-1.96/test/006-unoptdisposal.testie000066400000000000000000000006631475770763400206740ustar00rootroot00000000000000%script for i in 0 1 2 3; do gifsicle -O$i x.gif > y.gif gifdiff x.gif y.gif done %file -e x.gif R0lGODlhKAAUAPEAAP/////TAP8AAAAAACH5BAEUAAAALAAAAAAoABQAAAIahI+py+0Po5y02ouz 3rz7D4biSJbmiabqihUAIfkEARQAAAAsAAAAACgAFAAAAiiEj6nL7Q+jnLTai7PevPu/BeIYgAiJ mgaamiypvmMsl25Ny/m7t2YBACH5BAkUAAAALAAAAAAoABQAAAIohI+py+0Po5y02ouz3rz7vwni KIAIiZoGmposqb5jLJduTcv5u7dmAQAh+QQBFAAAACwAAAAAKAAUAAACGoSPqcvtD6OctNqLs968 +w+G4kiW5omm6ooVADs= gifsicle-1.96/test/007-alltransp.testie000066400000000000000000000010071475770763400177620ustar00rootroot00000000000000%script gifsicle -I < x.gif gifsicle x.gif | gifsicle -I gifsicle --scale=2 x.gif | gifsicle -I %file -e x.gif R0lGODlhAgACAHAAACH5BAEBAAAALAAAAAACAAIAAAIChFEAOw== %expect stdout * 1 image logical screen 2x2 + image #0 2x2 transparent 0 delay 0.01s * 1 image logical screen 2x2 global color table [2] background 0 + image #0 2x2 transparent 0 delay 0.01s * 1 image logical screen 4x4 global color table [2] background 0 + image #0 4x4 transparent 0 delay 0.01s gifsicle-1.96/test/008-resizemix.testie000066400000000000000000000031721475770763400200070ustar00rootroot00000000000000%script gifsicle --resize=250x_ strip.gif > stripx.gif gifsicle greenmini.gif stripx.gif > stripy.gif gifsicle -UO2 --crop=0,0+10x7 stripy.gif '#1' > overlay.gif gifsicle --crop=0,0+10x7 greenmini.gif > minibit.gif gifdiff overlay.gif minibit.gif %file -e greenmini.gif R0lGODdh+gAHAPAAAD3kxzzixSwAAAAA+gAHAAACP4SPqcvtD6OctL4AsN1V8w+G4kiW5omm6sq2 7gvH8kzX9o1vXs73fhPYBYfDn/FITCqXjCIumGBmPrtc9dgoAAA7 %file -e strip.gif R0lGODlhbgEKAPYAAAgEBQYIDQoJDgwLEwwMGhINFRkLFhIOGgsSFg0RHRUTHBkUGBoVHRsZHhQO IhQUIxoWIhYYJhwaIxwcKyIcJCkZJSIdKxwhKycjJyQiLCsjLDclKyMjMyslMiwqMysrPDMrNCsz Njk0Oy5IOjg3RkhESlZLVkxTVFhVW2VbZmhka3JqbGpodHd1e3l4hIeEiomIlZaUmaWbp6Kroail q7elqam2rqmotrartrm0u8e2usrJzNra3eXc5Ofm6PX27fDv8gAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH+ KEVkaXRlZCB3aXRoIEx1bmFQaWM6IGh0dHA6Ly9sdW5hcGljLmNvbS8AIfkEAQAAQQAsAAAAAG4B CgAAB/+AQYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKz tLW2t7iOP0G7vYK7vMHCi8DAhMbExkCFQMi5z9DRtkA8Ojk42DU2NNjc2dg4Njg1NTfaODc32NfX 4eDi69jw8uDoNznp1zQ0NjL7MzRy5NjhQ5rBgwhN9ZihYgQGDRgoSMyQoUFEDBcxdHiIIcNDiBo6 dAgJooOHDRo8hNTAkqWFDBU8XrhAwYKEmxIUUGBwoMEABSQwBBjQQEACDA1QvNjhLKHTp1Ad8Ygh ggIGEFg9eMBacqRJEFpbilQpcsNIDRk0xIQZcScFCHDDIShgoKCuggMHENxFgCDBTwIBQrg4kcGC hgEIGgAA0EDFjqiQI0sWBARGhwkePojswIEDSw4XPFBoUEIDAxAROAiQcKFBAwkTIEhgUADChAkS HuiGEEFBbwUJ6iZoYBe4gr4KHhxwcPdABBgvVLxAgeFC9RARWxSczL17NBwkOEzgIJv1hZsRYCuQ YCLGCxAGJMDlIGFBegcRbDKQP+Cm7AgA/hcBb74VZ+AAEQzg3HorsJDCdNhdgIAIITQgAg3eRRUI ADs= gifsicle-1.96/test/009-zerowidth.testie000066400000000000000000000101601475770763400200030ustar00rootroot00000000000000%script gifsicle zerowidth-ok.gif | gifsicle -I %file -e zerowidth-ok.gif R0lGODlhgAKQAZECAAAAAKqqqgAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFDwADACwAAAAA AAAAAQAC/oSPqcvtD6OctNqLs968+w+G4kiW5omm6sq27gvH8kzX9o3n+s73/g8MCofEovGITCqX zKbzCY1Kp9Sq9YrNarfcrvcLDovH5LL5jE6r1+y2+w2Py+f0uv2Oz+v3/L7/DxgoOEhYaHiImFgT wNjIqAgZKenl6Dh5iZmpucnZ6fkJGio6SlpqeoqaqrrK2ur6ChsrO0tba3uLm6u7y9vr+wscLDxM XGx8jJysvMzc7PwMHS09TV1tfY2drb3N3e39DR4uPk5ebn6Onq6+zt7u/g4fLz9PX29/j5+vv8/f 7/8PMKDAgQQLGjyIMKHChQwbOnwIMaLEiRQrWryIMaPG/o0cO3r8CDKkyJEkS5o8iTKlypUsW7p8 CTOmzJk0a9q8iTOnzp08e/r8CTSo0KFEixo9ijSp0qVMmzp9CjWq1KlUq1q9ijWr1q1cu3r9Cjas 2LFky5o9izat2rVs27p9Czeu3Ll069q9izev3r18+/r9Cziw4MGECxs+jDix4sWMGzt+DDmy5MmU K1u+jDmz5s2cO3v+DDq06NGkS5s+jTq16tWsW7t+DTu27Nm0a9u+jTu37t28e/v+DTy48OHEixs/ jjy58uXMmzt/Dj269OnUq1u/jj279u3cu3v/Dj68+PHky5s/jz69+vXs27t/Dz++/Pn069u/jz+/ /v38/vv7/w9ggAIOSGCBBh6IYIIKLshggw4+CGGEEk5IYYUWXohhhhpuyGGHHn4IYogijkhiiSae iGKKKq7IYosuvghjjDLOSGONNt6IY4467shjjz7+CGSQQg5JZJFGHolkkkouyWSTTj4JZZRSTkll lVZeiWWWWm7JZZdefglmmGKOSWaZZp6JZppqrslmm26+CWeccs5JZ5123olnnnruyWeffv4JaKCC DkpooYYeimiiii7KaKOOPgpppJJOSmmlll6Kaaaabsppp55+Cmqooo5Kaqmmnopqqqquymqrrr4K a6yyzkprrbbeimuuuu7Ka6++/gpssMIOS2yxxh6LFmyyyi7LbLPOPgtttNJOS2211mpZAAAAIfkE BQ8AAwAsAAAOAAgAAgAAAgSEjwkFADs= %file -e zerowidth-bad.gif R0lGODdhQgFCAfcAAAAAAFUAAIAAAKoAANUAAP8AAAArAFUrAIArAKorANUrAP8rAABVAFVVAIBV AKpVANVVAP9VAACAAFWAAICAAKqAANWAAP+AAACqAFWqAICqAKqqANWqAP+qAADVAFXVAIDVAKrV ANXVAP/VAAD/AFX/AID/AKr/ANX/AP//AAAAVVUAVYAAVaoAVdUAVf8AVQArVVUrVYArVaorVdUr Vf8rVQBVVVVVVYBVVapVVdVVVf9VVQCAVVWAVYCAVaqAVdWAVf+AVQCqVVWqVYCqVaqqVdWqVf+q VQDVVVXVVYDVVarVVdXVVf/VVQD/VVX/VYD/Var/VdX/Vf//VQAAgFUAgIAAgKoAgNUAgP8AgAAr gFUrgIArgKorgNUrgP8rgABVgFVVgIBVgKpVgNVVgP9VgACAgFWAgICAgKqAgNWAgP+AgACqgFWq gICqgKqqgNWqgP+qgADVgFXVgIDVgKrVgNXVgP/VgAD/gFX/gID/gKr/gNX/gP//gAAAqlUAqoAA qqoAqtUAqv8AqgArqlUrqoArqqorqtUrqv8rqgBVqlVVqoBVqqpVqtVVqv9VqgCAqlWAqoCAqqqA qtWAqv+AqgCqqlWqqoCqqqqqqtWqqv+qqgDVqlXVqoDVqqrVqtXVqv/VqgD/qlX/qoD/qqr/qtX/ qv//qgAA1VUA1YAA1aoA1dUA1f8A1QAr1VUr1YAr1aor1dUr1f8r1QBV1VVV1YBV1apV1dVV1f9V 1QCA1VWA1YCA1aqA1dWA1f+A1QCq1VWq1YCq1aqq1dWq1f+q1QDV1VXV1YDV1arV1dXV1f/V1QD/ 1VX/1YD/1ar/1dX/1f//1QAA/1UA/4AA/6oA/9UA//8A/wAr/1Ur/4Ar/6or/9Ur//8r/wBV/1VV /4BV/6pV/9VV//9V/wCA/1WA/4CA/6qA/9WA//+A/wCq/1Wq/4Cq/6qq/9Wq//+q/wDV/1XV/4DV /6rV/9XV///V/wD//1X//4D//6r//9X//z8/P2tra5WVlcPDw////ywAAAAAAABCAQcI/wABCBxI sKDBgwgTKlzIsKHDhxAjSpxIsaLFixgt/vuXsaPHjyBDihxJsuTFjQA4mlzJsqXLlzBjJkSpUqbN mzhz6pS5sebOn0CDCh16sKdPokiTKl0K0ijTp1CjSkXodKrVq1iBVs3KtavXklu/ih1LNmLYsmjT qj2rtq1brmzfyp3LNC7du3h/2s3Lt+/LvX4DCw4JeLDhwxQLI17MWKHixpAjP45MGfHkypgDX87M Ge/mzqDffg5NGu3o0qi/nk7NGuvq1rCjvo5NW+ns2riF3s7NW+fu3sB59gxOXPbw4shtH0/OXPfy 5tB9P49OXTjK6thj/s7O3eH27uBnTv8PT77j9/Loz6Mnr349+PbuucOPj30+fer270PPr585//7I /QcgcQIOCFyBBvKGYIK4LcggbQ4+CFuEErJGYYWoXYghaRpuCFqHHnIGYoiYjUgiZSaeCFmKKjLG YouWjQfjezLOKF+NNtaHY4747cjjfj7+6F+QQgZIZJEEHonkgUouqWCTTjYIZZQQTknlhFZeaWGW WmbIZZccfgnmh2KOKWKZZpaIZpoorsnmim6+6WKccsZ4XZ25vYinV3ruCRedfmoGaKB99UmoVYYe KlWiikLFaKN1DQrpXI9OmlSllhKFaabO3clpZ5t+qpekoppGaqlkhYoqTqqualOrrmr/d2qsXcFK q0u23spSrrqaxGuvJP0KrEjCDtvUrMYa52mygi7LbKHIPrtUsdKeFG21SFGLbWLXbtvpUd5S2m24 ozpLrlvanuvduOqyym67r74Lr6zmzpuqvPbiim++u+7Lr6/+/htswAITS3DBx9aLsGsHL2xeww5j lK7DEy9cMcIXF5yxwBv/2zG/H+cbsr0jz1syvCe3m7K6K5/bMrkvhxuztzNvWzO2N1ebs7Q786xw xFNBDLRGPw/taNFGRwpu0osizfSlTj+tadRSd1o1olRfXa7WTS/NdbZZf+2u12JbXfa0YZ9Nr9rK kc22dG6/PbbcU8dNt3V3m5331nvz/9033H/vJHTeg99dON2Hy53424uz3bjaj58dedmTi13515dz nbnWm1/dedWfSx3606MzXXrSpxud+tCrA916xK9TnHbghM1O+0exW2z77Q/bzfvAvv9ucPDCJ1z8 2sf/tXvyE+WO8fLMmwV99A85r/H01DdkPcfYZ7/Q9h537714xI/fvPjmGwQ+yOinT9D6IrfvvkDw kyz//PWbfL/7+aO8f/r9U9n/zBdAlg1wfAV02QG9l0CYLTB7DZTZA6kXQZpNMHoVtNkFmZdBnG0w eR3U2QePF0KflW9+6zohChlSwme1kFkvTFYMjTXDYdUQWDfsVQ51tcNb9ZBWP4xVEKBdNcRVFRFV RyxVEkW1xE81kVNPzFQULTXFSVURUldsVBYVtcVDdZFQXwxUGP00xj2VEU9nrFMa5bTGN7WRTW9M UxzNNMcx1RFMd+xSHrW0xyv1kUp/jFIgnTTIJRUSSYcsUiKFtMgfNZJHj8xRJG00yRlVEkaXbFEm VaTCFbKQJkYJpShHScpSmvKUqEylKlfJyla68pWwjKUsZ0lLWgakADs= %stdout * 2 images logical screen 640x400 global color table [2] background 0 loop forever + image #0 640x256 transparent 2 disposal asis delay 0.15s + image #1 8x2 at 0,14 transparent 2 disposal asis delay 0.15s gifsicle-1.96/test/010-warnings.testie000066400000000000000000000012071475770763400176060ustar00rootroot00000000000000%script export LANG=C gifsicle blank.gif -o /dev/null 2>&1 && echo blank worked gifsicle -w blank.gif -o /dev/null 2>&1 && echo blank worked gifsicle blank-garbage.gif -o /dev/null 2>&1 && echo blank-garbage worked gifsicle -w blank-garbage.gif -o /dev/null 2>&1 && echo blank-garbage worked %file -e blank.gif R0lGODlhAQABAIAAAAAAAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw== %file -e blank-garbage.gif R0lGODlhAQABAIAAAAAAAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAO2Rhc2prZm5zZGtqYW5m a2pkbnNha2YK %expect stdout blank worked blank worked gifsicle:blank-garbage.gif: warning: trailing garbage after GIF ignored blank-garbage worked blank-garbage worked gifsicle-1.96/test/011-resizemix.testie000066400000000000000000000032451475770763400200020ustar00rootroot00000000000000%script gifsicle -O2 --careful --resize-method=mix --resize=200x20 in.gif | gifdiff - want.gif %file -e in.gif R0lGODdhlgAPAPMOADhbmB89fxRBgSRCfilGgi1KhzFNiipQjDBUkDBWjDRXlC5dmDZckz1imSA9eAAAACwAAAAAlgAPAAAE/5DJSStLMmmtukcKIo7icSBmaqxsaxTwC8PEXBB4Tgz8kPe8gHBILBqPSIdyiRQul5aoZcPxWEmklPbg6hZeLNtMR8YBfbtBc802PpXrt6MhrU8S1nwHWzqZUFtdgitiYmWHZ2pti25vcW8NkZJ0dhgXenp8fFtcgl+fhWM0NYc/Z4yoQ3KPTwsLknUAABIKsphXmpudLLthYKFjpKVppolAqXJPjHKudhKytLcfIbl+1i69XsA3wcPFxkGoA8lwi8yuC9ATsuzt0nt7mlqAKoNgMsDe+sTgPUZnyZC9cdWOgq2Dtt6ByAVohAl7hH6FqjFKWBkfaHTwSHNGgL8iAKWVjCu3bCDBdigBvPvAcEufhwYefvEEats+RP2MJVkyLlzJVulSolxJjSEKeoEgisGXz1tGMjk7IhnpCJUcoUKlLTSKhVO2iExjbKt481tUAQJSqS2CNeFKrvI4eSI0dmJZqFGPrd3bVqUHV5jgdvU6U2ldQ3fN5tzLt62VBVoFv0wKke5hsjecgvOYKC1jtX07oIsseTJMF4XDXC6TuVTezp/VRgAAOw== %file -e want.gif R0lGODdhyAAUAPMOAD1imR89fxRBgSRCfilGgi1KhzFNiipQjDBUkDBWjDRXlC5dmDZckyA9eDhbmAAAACwAAAAAyAAUAAAE/5DJSatNSeKNlf8eEiJkWR4ogq6o4b5wXMyFQd/EXRB87w9A4C8YDBiPyKRyyWweG9Ao1PmURi3YLIWzAXlHJhNr3IqZX7UZWofzuYdEIaFIrdup1qg9D9X6LQgcJF9eYWFkY2eKNWZsNG+QPHEDPXR3l5gBfA17fAB/oFsihF+GJykrKoiKrGuOOpGxk0CZtUybnXkAu7y8oRoTCaOkhabGiAetNi5pr7CPOTuxlbO21ki4dZsLC72ffw4OEyLhw8RgxqYrZ8lnjM3O0DPT1HCzk9eam1a129y84sAFZOAh3Lli6dSVidGuETMb8eRJoyeH2j181vZJseVvwS8J4f8mHCyE7piqAyfZKWMWcV4bem8uVrM2QOOUfny46Qw3ECTPnz9HfhiW8CQqMishsmwJsymcOTKJMJmk8VpHoBV4KtAaVKiCUUVPnmq4shlEZ9Hm9ZgYidKctkKiSl1CdZ/VnNywUuBqcGtfoWFJpBqb9OGyiGnZsoXk1m3MuHJp0Y0jBUiUAXfz6FwAtHNXr1/DIhq7sAwjVmrOInZKMfI9PJVrWsKpOa/n26BDB1ZhdFVhNktXN3UM1zURAVRq2s3I57bzv8TA7j6EjGwrR8HjsW5tPIgA5PnCJ3mOe6SI6Qp9u3vXUvh2xt29gxdPnzx0r+iPIUPNvj3a97LENxtaffnY58AX3JyTH3XVnZaUf/8B6IaAkhEYnoEImregYNVZ9yCEbEhoEYXzWXgNhiAkeNCGpCFVGAwgwuKDWjBReJyJF9r3wWYassihi+stEmMkNE5j4404XhMBADs= gifsicle-1.96/test/testie000077500000000000000000001531071475770763400154730ustar00rootroot00000000000000#! /usr/bin/perl -w use Symbol 'qualify_to_ref'; use IO::Handle; use Errno; use POSIX ":sys_wait_h"; use MIME::Base64; use Time::HiRes qw(gettimeofday); no locale; use bytes; require 5.006; ($preserve_temporaries, $expand_mode, $verbose) = (0, 0, 0); $running_pid = 0; %require_error_commands = (); $quiet_ebadf = 0; ## utilities sub index2 ($$;$) { my($result) = (defined($_[2]) ? index($_[0], $_[1], $_[2]) : index($_[0], $_[1])); $result = length $_[0] if $result < 0; $result; } sub shquote ($) { my($t) = @_; $t =~ s/\'/\'\"\'\"\'/g; "'$t'"; } sub min (@) { my($m) = pop @_; foreach my $mm (@_) { $m = $mm if $mm < $m; } $m; } ## testie ipc sub tipc_write ($$;$$) { my($fh, $command, $arg, $noflush) = @_; die "!" if $command !~ /\A[A-Z]\z/; $arg = "" if !defined($arg); # print STDERR "$$ write $command $arg\n"; print $fh $command, length($arg), " ", $arg, "\n"; $fh->flush if !$noflush; } sub tipc_error () { if ($! == 0 || $!{EINTR} || $!{EAGAIN} || $!{EWOULDBLOCK}) { return; } elsif ($!{EBADF} && $::quiet_ebadf) { exit(0); } else { die "testie: ipc error: $!"; } } sub tipc_read ($$) { my($fh, $bufref) = @_; my($n, $x); while (1) { # does the buffer contain a valid command? if ($$bufref =~ /\A(\s*([A-Z])(\d+) )/ && length($$bufref) >= length($1) + $3) { my($v) = substr($$bufref, length($1), $3); $$bufref = substr($$bufref, length($1) + $3); return ($2, $v); } # if not try to read more data $x = ""; $n = sysread($fh, $x, 4096); tipc_error if !defined($n); return () if !$n; $$bufref .= $x; } } ## testie error handler object package TestieErrorHandler; sub new (;$) { my($print_context) = @_; bless ["", $print_context], TestieErrorHandler; } sub message ($@) { my($teh) = shift @_; print STDERR $teh->[0], @_; $teh->[0] = ""; } sub showmessage ($@) { my($teh) = shift @_; print @_; } sub context ($@) { my($teh) = shift @_; if ($teh->[1]) { # print_context my($t) = join("", @_); print STDERR $teh->[0], $t; $teh->[0] = "\r" . (" " x length($t)) . "\r"; } } sub clear ($) { } sub complete ($$) { } ## testie error handler object package TestieChildErrorHandler; sub new ($) { my($fh) = @_; bless ["", "", $fh], TestieChildErrorHandler; } sub message ($@) { my($eh) = shift @_; $eh->clear if $eh->[0] ne "E" && $eh->[1] ne ""; $eh->[0] = "E"; $eh->[1] .= join("", @_); } sub showmessage ($@) { my($eh) = shift @_; $eh->clear if $eh->[0] ne "S" && $eh->[1] ne ""; $eh->[0] = "S"; $eh->[1] .= join("", @_); } sub context ($@) { my($eh) = shift @_; $eh->clear if $eh->[1] ne ""; my($fh, $t) = ($eh->[2], join("", @_)); ::tipc_write($fh, "C", $t); } sub clear ($) { my($eh) = shift @_; ::tipc_write($eh->[2], $eh->[0], $eh->[1], 1) if $eh->[1] ne ""; $eh->[0] = ""; $eh->[1] = ""; } sub complete ($$) { my($eh, $tctr) = @_; $eh->clear; my(@t, $k, $v); while (($k, $v) = each %$tctr) { my($t) = "\"" . quotemeta($k) . "\" => "; if (ref($v)) { $t .= "[" . join(", ", map { "\"".quotemeta($_)."\"" } @$v) . "]"; } else { $t .= $v; } push @t, $t; } ::tipc_write($eh->[2], "T", "{" . join(", ", @t) . "}"); } ## testie error counter object package TestieCounter; my @counters = ("errors", "require_errors", "test_attempts", "test_skips", "test_failures", "bad_files"); sub new () { my($tctr) = bless { "require_error_commands" => [] }, TestieCounter; foreach my $x (@counters) { $tctr->{$x} = 0; } $tctr; } sub add ($$) { my($tctr, $tctr1) = @_; foreach my $x (@counters) { $tctr->{$x} += $tctr1->{$x}; } push @{$tctr->{"require_error_commands"}}, @{$tctr1->{"require_error_commands"}}; $tctr; } ## main testie test object package Testie; ## read testie file my %_special_filerefs = ('stdin' => 1, 'stdout' => 2, 'stderr' => 2); %_variables = (); $timeout = 45; sub _get ($;$) { my($tt, $acrossfiles) = @_; my($lines) = $tt->{"_data"}; my $t; while (defined($t = shift @$lines)) { if (!ref $t) { ++$tt->{"_line"}; last; } elsif ($acrossfiles) { $tt->{"_file"} = $t->[0]; $tt->{"_line"} = $t->[1]; } else { unshift @$lines, $t; $t = undef; last; } } $t; } sub _unget ($$) { my($tt, $t) = @_; if (defined($t) && $t ne "") { unshift @{$tt->{"_data"}}, $t; --$tt->{"_line"}; } } # return a command at a given line number sub command_at ($$;$) { my($tt, $lineno, $script_type) = @_; return undef if !defined($lineno); $lineno =~ s/^\s*|\s*$//g; $script_type = 'script' if !defined($script_type); my($lineno_arr) = $tt->{$script_type . '_lineno'}; for ($i = 0; $i < @$lineno_arr; $i++) { return $tt->{$script_type}->[$i] if $lineno_arr->[$i] eq $lineno; } undef; } # report an error sub eh ($) { my($tt) = @_; $tt->{"_eh"}; } sub file_err ($$;$) { my($tt, $text, $lineno) = @_; $text .= "\n" if $text !~ /\n$/s; $lineno = $tt->{"_line"} if !defined($lineno); $tt->eh->message($tt->{"_file"}, ":", $lineno, ': ', $text); $tt->{'err'}++; } sub _shell_split (\@$\@$$) { my($arr, $fn, $lineno_arr, $text, $lineno) = @_; my($qf, $qb, $func, $out) = (0, 0, 0, ''); my($sq, $dq, $bq, $nl, $hh, $lb, $rb) = (-2, -2, -2, -2, -2, -2, -2); my($first, $pos) = (0, 0); $lineno -= ($text =~ tr/\n//); while ($pos < length $text) { $sq = ::index2($text, "\'", $pos) if $sq < $pos; $dq = ::index2($text, "\"", $pos) if $dq < $pos; $bq = ::index2($text, "\`", $pos) if $bq < $pos; $nl = ::index2($text, "\n", $pos) if $nl < $pos; $hh = ::index2($text, "#", $pos) if $hh < $pos; $lb = ::index2($text, "{", $pos) if $lb < $pos; $rb = ::index2($text, "}", $pos) if $rb < $pos; if ($qf == 1) { $qf = 0 if $sq < length $text; $out .= substr($text, $pos, $sq + 1 - $pos); $pos = $sq + 1; next; } elsif ($qf == 2) { $qf = 0 if $dq < length $text; $out .= substr($text, $pos, $dq - $pos) . '"'; $pos = $dq + 1; next; } # find minimum my($min) = ::min($sq, $dq, $bq, $nl, $hh, $lb, $rb); $out .= substr($text, $pos, $min - $pos) . substr($text, $min, 1); if ($sq == $min) { $qf = 1; $pos = $sq + 1; } elsif ($dq == $min) { $qf = 2; $pos = $dq + 1; } elsif ($bq == $min) { $qb = !$qb; $pos = $bq + 1; } elsif ($lb == $min) { $func++; $pos = $lb + 1; } elsif ($rb == $min) { $func--; $pos = $rb + 1; } elsif ($hh == $min) { $out .= substr($text, $min + 1, $nl - $min); $lineno++; $pos = $nl + 1; } elsif (!$qb && !$func && ($nl == $pos || substr($text, $nl - 1, 1) ne "\\")) { push @$arr, $out; push @$lineno_arr, "$fn:$lineno"; $out = ''; $lineno += (substr($text, $first, $nl - $first + 1) =~ tr/\n//); $first = $pos = $nl + 1; } else { $pos = $nl + 1; } } if ($first < length $text) { push @$arr, $out; push @$lineno_arr, "$fn:$lineno"; } if ($qf == 1) { "unmatched single quote"; } elsif ($qf == 2) { "unmatched double quote"; } elsif ($qb) { "unmatched backquote"; } else { ""; } } sub _read_text ($) { my($tt) = @_; my($r, $t) = (''); while (defined($t = $tt->_get())) { last if $t =~ /^\%/; $t =~ s/^\\\%/\%/; $r .= $t; } $tt->_unget($t); $r; } sub _read_text_into ($$) { my($tt, $section) = @_; $tt->{$section} = '' if !defined($tt->{$section}); $tt->{$section} .= $tt->_read_text(); } sub _read_script_section ($$$) { my($tt, $args, $script_type) = @_; my($lineno_type, $quiet_type) = ($script_type . '_lineno', $script_type . '_quietline'); $tt->{$lineno_type} = [] if !exists $tt->{$lineno_type}; $tt->{$quiet_type} = {} if !exists $tt->{$quiet_type}; my($quiet); if ($script_type eq 'require' & $args eq '-q') { $quiet = 1; } elsif ($args ne '') { $tt->file_err("arguments to '\%$script_type' ignored"); } #$tt->file_err("multiple '\%$script_type' sections defined") if $tt->{$script_type}; my($r) = $tt->_read_text(); my $count = @{$tt->{$lineno_type}}; my($what) = _shell_split(@{$tt->{$script_type}}, $tt->{"_file"}, @{$tt->{$lineno_type}}, $r, $tt->{"_line"} + 1); $tt->file_err("$what in '\%$script_type'") if $what ne ''; while ($quiet && $count < @{$tt->{$lineno_type}}) { my($line) = $tt->{$lineno_type}->[$count++]; $tt->{$quiet_type}->{$line} = 1; } } sub braces_to_regex ($$) { my($x, $mode) = @_; my($re, $message) = ("", undef); while ($x =~ /\A(.*?)\{\{(.*?)\}\}(.*)\z/) { my($before, $middle, $after) = ($1, $2, $3); if ($middle =~ /\A\?/) { $before =~ s/\s+\z//; $middle =~ s/\A\?\s*//; $middle =~ s/\s+\z//; $after =~ s/\A\s+//; $message = (defined($message) ? $message . " " . $middle : $middle); $x = $before . $after; } else { $before = quotemeta($before) if $mode == 1; $middle =~ s,(\A|[^\\]|\\\\)/,$1\\/,g; # not 100% right sadly $re .= $before . "(?:" . $middle . ")"; $x = $after; } } $x = quotemeta($x) if $mode == 1; wantarray ? ($re . $x, $message) : $re . $x; } sub _read_file_section ($$$$;$) { my($tt, $args, $secname, $prefix, $backup_file) = @_; $args =~ s/\s+$//; # split arguments to get fileref my(@args) = split(/\s+/, $args); # assert that we understand $secname die if $secname ne 'file' && $secname ne 'expect' && $secname ne 'expectv' && $secname ne 'expectx' && $secname ne 'ignore' && $secname ne 'ignorex' && $secname ne 'ignorev'; # check for alternates and length my($alternate, $delfirst, $whitespace, $base64, $regex_opts, $length) = (0, 0, 0, 0, '', undef); while (@args) { if ($args[0] =~ /\A-a/) { $alternate = 1; } elsif ($args[0] =~ /\A-d/) { $delfirst = 1; } elsif ($args[0] =~ /\A-i/) { $regex_opts .= "(?i)"; } elsif ($args[0] =~ /\A-e/) { $base64 = 1; } elsif ($args[0] =~ /\A-w/) { $whitespace = 1; } elsif ($args[0] =~ /\A\+(\d+)\z/) { $length = $1; } else { last; } $args[0] = "-$1" if $args[0] =~ /\A-.(.*)\z/; shift @args if $args[0] !~ /\A-./; } # make sure there are filerefs if (!@args && $backup_file) { push @args, $backup_file; } elsif (!@args) { push @args, "stdin" if $secname eq 'file'; push @args, "stdout" if $secname eq 'expect' || $secname eq 'expectv' || $secname eq 'expectx'; push @args, "all" if $secname eq 'ignore' || $secname eq 'ignorev' || $secname eq 'ignorex'; } # complain about '%file -aiw' if (($secname eq 'file' || $secname eq 'ignore' || $secname eq 'ignorev' || $secname eq 'ignorex') && $alternate) { $tt->file_err("'\%$secname -a' is illegal"); } if (($secname eq 'file' || $secname eq 'expectv') && $regex_opts) { $tt->file_err("'\%$secname -i' is illegal"); } if (($secname eq 'file' || $secname eq 'expectv') && $whitespace) { $tt->file_err("'\%$secname -w' is illegal"); } $secname .= "v" if $secname eq "expect" && $base64; if (($secname eq "filex" || $secname eq "expectx" || $secname eq "ignore" || $secname eq "ignorev" || $secname eq "ignorex") && $base64) { $tt->file_err("'\%$secname -e' is illegal"); } # read contents my($seclineno) = $tt->{"_line"}; my($firstline) = $tt->{"_file"} . ":" . ($seclineno + 1); my($file_data) = ""; if (defined($length)) { my($t); while (length($file_data) < $length && defined($t = $tt->_get())) { $file_data .= $t; if (length($file_data) > $length) { # save extra data from the first line $tt->_unget(substr($t, $length - length($file_data))); $file_data = substr($file_data, 0, $length); } } $tt->file_err("file too short", $seclineno) if length($file_data) != $length; } else { $file_data = $tt->_read_text(); } # modify contents based on flags $alternate = 1 if $secname eq 'ignore' || $secname eq 'ignorev' || $secname eq 'ignorex'; # 'ignore' always behaves like -a if ($delfirst) { $file_data =~ s{^.}{}mg; } if (($secname eq 'ignore' || $secname eq 'ignorev' || $secname eq 'ignorex') && $whitespace) { $file_data =~ tr/ \f\r\t\013//d; } if ($secname eq 'ignore') { $file_data =~ s{^(.+)}{braces_to_regex($1, 1)}meg; } elsif ($secname eq 'ignorev') { $file_data =~ s{^(.+)}{quotemeta($1)}meg; } elsif ($secname eq 'ignorex') { $file_data =~ s[\s*\{\{\?.*?\}\}\s*][]mg; } if ($regex_opts && $secname eq 'expect') { $file_data =~ s{\{\{}{\{\{$regex_opts}g; } elsif ($regex_opts) { $file_data =~ s{^(?=.)}{$regex_opts}mg; } if ($base64) { $file_data = MIME::Base64::decode_base64($file_data); } # stick contents where appropriate my($fn); foreach $fn (@args) { if (($fn eq 'stdin' && $secname ne 'file') || (($fn eq 'stdout' || $fn eq 'stderr') && $secname eq 'file') || ($fn eq 'all' && ($secname ne 'ignore' && $secname ne 'ignorev' && $secname ne 'ignorex'))) { $tt->file_err("'$fn' not meaningful for '\%$secname'", $seclineno); } my($hashkey) = $prefix . ":" . $fn; if (!($fn =~ m,\A[-A-Za-z_0-9.]+\z, || ($fn =~ m,\A[-A-Za-z_0-9./]+\z, && $fn !~ m,(\A\.\./|/\.\./|/\.\.\z|\A/|//|/\z),))) { $tt->file_err("bad filename '\%$secname $fn'", $seclineno); next; } elsif (!exists($tt->{$hashkey})) { push @{$tt->{$secname}}, $fn; $tt->{$hashkey} = []; } elsif (!$alternate) { $tt->file_err("'\%$secname $fn' already defined", $seclineno); } push @{$tt->{$hashkey}}, $file_data; my($num) = @{$tt->{$hashkey}} - 1; $tt->{"F:$fn"} = 1; $tt->{"firstline:$hashkey:$num"} = $firstline; $tt->{"whitespace:$hashkey:$num"} = 1 if $whitespace; } } sub _skip_section ($) { my($tt) = @_; my($t); while (defined($t = $tt->_get())) { last if $t =~ /^%/; } $tt->_unget($t); } sub parse ($) { my($tt) = @_; my($t, $read_command); # delete garbage my(@deletes, $k, $v); while (($k, $v) = each %$tt) { push @deletes, $k if $k ne "_data" && $k ne "err" && $k ne "_eh"; } foreach $k (@deletes) { delete $tt->{$k}; } while (defined($t = $tt->_get(1))) { if ($t =~ /^%\s*(\w+)\s*(.*?)\s*$/) { my($command) = lc($1); my($args) = $2; if ($command eq 'script' || $command eq 'test') { $tt->_read_script_section($args, 'script'); } elsif ($command eq 'require') { $tt->_read_script_section($args, 'require'); } elsif ($command eq 'info') { $tt->file_err("arguments to '\%info' ignored") if $args ne ''; $tt->_read_text_into('info'); } elsif ($command eq 'desc') { $tt->file_err("arguments to '\%desc' ignored") if $args ne ''; $tt->_read_text_into('info'); } elsif ($command eq 'cut') { $tt->_read_text_into('cut'); } elsif ($command eq 'stdin' || $command eq 'input') { $tt->_read_file_section($args, "file", "f", "stdin"); } elsif ($command eq 'file') { $tt->_read_file_section($args, 'file', 'f'); } elsif ($command eq 'stdout' || $command eq 'output') { $tt->_read_file_section($args, "expect", "e", "stdout"); } elsif ($command eq 'stderr') { $tt->_read_file_section($args, "expect", "e", "stderr"); } elsif ($command eq 'expect') { $tt->_read_file_section($args, 'expect', 'e'); } elsif ($command eq 'expectx') { $tt->_read_file_section($args, 'expectx', 'x'); } elsif ($command eq 'expectv' || $command eq 'expect_verbatim' || $command eq 'verbatim') { $tt->_read_file_section($args, 'expectv', 'v'); } elsif ($command eq 'ignore') { $tt->_read_file_section($args, 'ignore', 'i'); } elsif ($command eq 'ignorev') { $tt->_read_file_section($args, 'ignorev', 'i'); } elsif ($command eq 'ignorex') { $tt->_read_file_section($args, 'ignorex', 'i'); } elsif ($command eq 'include') { if ($args !~ /^\//) { my($oldfn) = $tt->{"_file"}; $oldfn =~ s/(\A|\/)[^\/]+\z/$1/; $args = $oldfn . $args; } if (open(INCLUDE, "<", $args)) { my(@ilines, $it); push @ilines, [$args, 0]; push @ilines, $it while defined($it = ); push @ilines, [$tt->{"_file"}, $tt->{"_line"}]; unshift @{$tt->{"_data"}}, @ilines; } else { $tt->file_err("\%include $args: $!"); } } elsif ($command eq 'eot') { unshift @{$tt->{"_data"}}, [$tt->{"_file"}, $tt->{"_line"}]; $tt->{"continue"} = 1; last; } elsif ($command eq 'eof') { 1 while defined($t = $tt->_get()); } else { $tt->file_err("unrecognized command '$command'"); $tt->_skip_section(); } $read_command = 1; } else { if ($t =~ /^%/) { $tt->file_err("bad '\%' command"); } elsif ($t !~ /^[\#!]/ && $t =~ /\S/) { $tt->file_err("warning: garbage ignored") if $read_command; $read_command = 0; } } } $tt; } sub read (*$;$) { my($fh, $teh, $fn) = @_; $fh = ::qualify_to_ref($fh, caller); my($t, $tt); $tt = bless { "err" => 0, "_data" => [[$fn, 0]], "_eh" => $teh }, Testie; push @{$tt->{"_data"}}, $t while defined($t = <$fh>); $tt->parse(); $tt; } sub have_file ($$) { my($tt, $fileref) = @_; exists($tt->{"F:$fileref"}); } sub empty ($) { my($tt) = @_; !exists($tt->{'script'}); } sub save_files ($&) { my($tt, $fileref_subr) = @_; my($fn, $dirn, $actual); # create implied subdirectories foreach $fn (keys %$tt) { next if $fn !~ m,\AF:(.*)/([^/]*)\z,; $dirn = $1; while (!-d $fileref_subr->($dirn)) { $fn = $dirn; $fn = $1 while ($fn =~ m,\A(.*)/([^/]*)\z, && !-d $fileref_subr->($1)); $actual = $fileref_subr->($fn); mkdir $actual || die "$actual: $!\n"; } } # write '%file' contents foreach $fn (@{$tt->{'file'}}) { $actual = $fileref_subr->($fn); next if !defined($actual); open OUT, ">", $actual || die "$actual: $!\n"; print OUT $tt->{"f:$fn"}->[0]; close OUT; } } sub script_text ($&$) { my($tt, $fileref_subr, $script_type) = @_; my($subbody, $var, $val) = ''; my($t) = ''; if (!$::expand_mode) { $t .= <<'EOD;'; testie_failed () { exitval=$? test $exitval = 0 || (echo; echo testie_failure:$exitval) >&2 exit $exitval } testie_subtest () { echo testie_subtest "$@" echo testie_subtest "$@" >&2 } trap testie_failed EXIT EOD; } my($scriptarr, $linenoarr) = ($tt->{$script_type}, $tt->{$script_type . "_lineno"}); my($last_unfinished) = 0; foreach my $i (0..$#{$tt->{$script_type}}) { my($ln, $text) = ($linenoarr->[$i], $scriptarr->[$i]); if (!$::expand_mode && !$last_unfinished) { $t .= "echo >&2; echo testie_lineno:$ln >&2\n"; } my(@c, @d); _shell_split(@c, "", @d, $text, 0); die if @c != 1; chomp $c[0]; next if $c[0] =~ /^\s*$/s; $last_unfinished = ($c[0] =~ /(?:\&\&|\|\||\|)\s*\z/); $c[0] =~ s,^(\s*)\./,$1../, if !$::expand_mode; $t .= $c[0] . "\n"; } $t; } sub output_error ($$$$) { my($tt, $fileref_subr, $script_type, $tctr) = @_; my($fp) = $tt->{'errprefix'}; if (!open(ERR, "<", $fileref_subr->('stderr'))) { $tt->eh->message($fp, $!, "\n"); ++$tctr->{"errors"}; return $tctr; } my($errortext, $subtest, $t, $lineno, $failure) = ('', ''); while ($t = ) { if ($t =~ /^testie_lineno:(.*)$/) { $lineno = $1; $errortext = ''; } elsif ($t =~ /^testie_failure:(.*)$/) { $failure = $1; } elsif ($t =~ /^testie_subtest (.*)$/) { $subtest = " subtest $1"; } else { $errortext .= $t; } } close ERR; $lineno = $fp if !defined($lineno); $lineno =~ s/: *\z//; my($failure_text); if (!defined($failure)) { $failure_text = "undefined error"; } elsif ($failure eq "timeout") { $failure_text = "timed out after $Testie::timeout sec"; } elsif ($failure == 1) { $failure_text = "failure"; } else { $failure_text = "error $failure"; } if (defined($script_type) && $script_type eq 'require') { $failure_text = "requirement $failure_text"; ++$tctr->{"require_errors"}; } else { ++$tctr->{"errors"}; } $errortext =~ s/\s*\z//; my($cmd) = $tt->command_at($lineno, $script_type); # exit early if quiet return $tctr if $tt->{$script_type . '_quietline'}->{$lineno} && $::verbose <= 0; $lineno .= $subtest; if ($errortext =~ /^testie_error:/) { while ($errortext =~ /^testie_error:([^\n]*)/g) { $tt->eh->message($lineno, ": ", $1, "\n"); } $errortext =~ s/^testie_error:([^\n]*)//g; $errortext =~ s/\s*//; $tt->eh->message($lineno, ": (There were other errors as well.)\n") if $errortext ne ''; } elsif (!defined($cmd)) { $tt->eh->message($lineno, ": $failure_text at undefined point in script\n"); } else { $cmd =~ s/^\s*|\s*$//g; $cmd =~ s/([\000-\037])/'^' . chr(ord($1) + ord('@'))/eg; $cmd =~ s/([\177-\377])/"\\" . sprintf("%03o", ord($1))/eg; if (length($cmd) > 40) { $cmd = substr($cmd, 0, 40) . "..."; } # if nonverbose requirement, remember command, don't print error if (defined($script_type) && $script_type eq 'require' && $::verbose <= 0) { push @{$tctr->{"require_error_commands"}}, $cmd; } else { $tt->eh->message($lineno, ": $failure_text at '$cmd'\n"); while ($errortext =~ /([^\n]*)/g) { $tt->eh->message($lineno, ": $1\n") if $1 ne ''; } } } $tctr; } sub _output_expectation_error ($$$$$) { my($fp, $efn, $etrack, $teh, $tctr) = @_; # fix subtest description if (defined($etrack->{"subtest"})) { $fp =~ s/: \z/ /; $fp .= "subtest " . $etrack->{"subtest"} . ": "; } if (defined($etrack->{"expectedline"})) { $fp = $etrack->{"expectedline"} . ": "; } # output message if ($efn eq 'stdout') { $teh->message($fp, "standard output has unexpected value starting at line " . $etrack->{"textline"} . "\n"); } elsif ($efn eq 'stderr') { $teh->message($fp, "standard error has unexpected value starting at line " . $etrack->{"textline"} . "\n"); } else { $teh->message($fp, "file $efn has unexpected value starting at line " . $etrack->{"textline"} . "\n"); } # output expected and text data if possible $etrack->{"expected"} = "" if $etrack->{"expected"} eq "\376"; $etrack->{"expected"} =~ s/\r?\n?\z//; $etrack->{"text"} = "" if $etrack->{"text"} eq "\376"; $etrack->{"text"} =~ s/\r?\n?\z//; if ($etrack->{"expected"} =~ /\A[\t\040-\176]*\z/ && $etrack->{"text"} =~ /\A[\t\040-\176]*\z/) { $etrack->{"expected"} =~ s/\s*\{\{\?.*?\}\}\s*//g if $etrack->{"mode"} != 0; $teh->message($fp, $efn, ":", $etrack->{"textline"}, ": expected '", $etrack->{"expected"}, "'\n", $fp, $efn, ":", $etrack->{"textline"}, ": but got '", $etrack->{"text"}, "'\n"); } if (defined($etrack->{"message"})) { $teh->message($fp, $efn, ":", $etrack->{"textline"}, ": ", $etrack->{"message"}, "\n"); } # maintain error count ++$tctr->{"errors"}; return $tctr; } sub _expect_trim_whitespace ($) { my($out) = ""; foreach my $x (split(/(\{\{.*?\}\})/, $_[0])) { $x =~ tr/ \f\r\t\013//d if $x !~ /\A\{\{/; $out .= $x; } return $out; } sub _check_one_typed_expect ($$$$$) { my($tt, $raw_text, $fn, $ignores, $etrack) = @_; my($mode) = ($fn =~ /^v/ ? 0 : ($fn =~ /^e/ ? 1 : 2)); my($expnum) = 0; foreach my $exp (@{$tt->{$fn}}) { my($text) = $raw_text; my($whitespace) = $tt->{"whitespace:$fn:$expnum"}; # escape in common case return 0 if $text eq $exp; # check that files really disagree (in later modes) if ($mode > 0) { # ignore differences in amounts of vertical whitespace $text =~ s/[ \f\r\t\013]+\n/\n/g; $text =~ s/\n\n+\z/\n/; $text =~ s/\A\n//; $exp =~ s/[ \f\r\t\013]+\n/\n/g; $exp =~ s/\n\n+\z/\n/; return 0 if $text eq $exp; # ignore explicitly ignored text $text = $ignores->($text) if $ignores; } # line-by-line comparison my(@tl) = (split(/\n/, $text), "\376"); my(@el) = (split(/\n/, $exp), "\376"); my($tp, $ep, $subtest, $message) = (0, 0, undef, undef); while ($tp < @tl && $ep < @el) { # skip blank lines and ignored lines ++$ep while $el[$ep] eq '' && $mode > 0; ++$tp while ($tl[$tp] eq '' && $mode > 0) || $tl[$tp] eq "\377"; # process testie_subtest if (length($tl[$tp]) > 15 && substr($tl[$tp], 0, 15) eq "testie_subtest ") { $subtest = substr($tl[$tp], 15); $tp++; next; } # compare lines my($tline, $eline) = ($tl[$tp], $el[$ep]); if ($whitespace) { $tline =~ tr/ \f\r\t\013//d; $eline = _expect_trim_whitespace($eline); } if ($mode != 0 && $eline =~ /\{\{/) { my($re); ($re, $message) = braces_to_regex($eline, $mode); last if $tline !~ m/\A$re\z/; } elsif ($mode == 2) { last if $tline !~ m/\A$eline\z/; } elsif ($tline ne $eline) { last; } $tp++, $ep++; } return 0 if $tp >= @tl || $ep >= @el; if (!defined($etrack->{"textline"}) || $tp + 1 > $etrack->{"textline"}) { $etrack->{"text"} = $tl[$tp]; $etrack->{"expected"} = $el[$ep]; $etrack->{"textline"} = $tp + 1; if (defined($tt->{"firstline:$fn:$expnum"}) && $tt->{"firstline:$fn:$expnum"} =~ /^(.*):(\d+)$/) { $etrack->{"expectedline"} = $1 . ":" . ($2 + $ep); } else { $etrack->{"expectedline"} = undef; } $etrack->{"mode"} = $mode; $etrack->{"subtest"} = $subtest; $etrack->{"message"} = $message; } ++$expnum; } return -1; } sub _create_ignores ($$) { my($tt, $efn) = @_; my($ignores, $wignores, $body) = ("", ""); foreach my $fn ($efn, "all") { next if !exists($tt->{"i:$fn"}); for (my $expnum = 0; $expnum < @{$tt->{"i:$fn"}}; ++$expnum) { if ($tt->{"whitespace:i:$fn:$expnum"}) { $wignores .= $tt->{"i:$fn"}->[$expnum] . "\n"; } else { $ignores .= $tt->{"i:$fn"}->[$expnum] . "\n"; } } } # ignore testie messages $ignores .= "testie_lineno:.*\ntestie_error:.*\n" if $efn eq "stderr"; if ($ignores eq "" && $wignores eq "") { return undef; } elsif ($wignores eq "") { $ignores =~ s{^([ \t]*\S[^\n]*)}{\$t =~ s/^$1\[ \\t\]*\$/\\377/mg;}mg; $body = "sub (\$) { my(\$t) = \@_; $ignores \$t; }\n"; } else { $ignores =~ s{^([ \t]*\S[^\n]*)}{s/\\A$1\[ \\t\]*\\z/\\377/;}mg; $wignores =~ s{^(\S[^\n]*)}{\$_ = "\\377" if \$x =~ m/\\A$1\\z/;}mg; $body = "sub (\$) { my(\$t) = \@_; my(\$x); join(\"\\n\", map { " . "\$x = \$_; \$x =~ tr/ \\f\\r\\t\\013//d;\n$ignores$wignores " . "\"\$_\\n\" } split /\\n/, \"\$t\\n\"); }\n"; } return eval($body); } sub _check_one_expect ($$$$) { my($tt, $fileref_subr, $efn, $tctr) = @_; my($fp) = $tt->{'errprefix'}; my($etrack) = {}; # read file text if (!open(IN, "<", $fileref_subr->($efn))) { $tt->eh->message($fp, $efn, ": ", $!, "\n"); ++$tctr->{"errors"}; return 0; } my($raw_text) = ; $raw_text = '' if !defined($raw_text); close IN; # prepare $ignores my($ignores) = _create_ignores($tt, $efn); # now compare alternates foreach my $fn ("v:$efn", "e:$efn", "x:$efn") { return 0 if _check_one_typed_expect($tt, $raw_text, $fn, $ignores, $etrack) >= 0; } # if we get here, none of the attempts matched _output_expectation_error($fp, $efn, $etrack, $tt->eh, $tctr); } sub check_expects ($$$) { my($tt, $fileref_subr, $tctr) = @_; my($fp) = $tt->{'errprefix'}; local($/) = undef; my($expectx) = 0; my($tp, @tl, $ep, @el); # check expected files my(%done); foreach my $efn (@{$tt->{'expect'}}, @{$tt->{'expectx'}}, @{$tt->{'expectv'}}) { next if $done{$efn}; _check_one_expect($tt, $fileref_subr, $efn, $tctr); $done{$efn} = 1; } 0; } package main; my($dir, @show, $show_stdout, $show_stderr, %child_pids); my($SHELL) = "/bin/sh"; sub script_fn_to_fn ($) { my($fn) = @_; $fn; } sub out_script_fn_to_fn ($) { my($fn) = @_; "$dir/$fn"; } sub _shell ($$$$$) { my($dir, $scriptfn, $stdin, $stdout, $stderr) = @_; $scriptfn = "./$scriptfn" if $scriptfn !~ m|^/|; # Create a new process group so we can (likely) kill any children # processes the script carelessly left behind. Thanks, Chuck Blake! my($child_pid) = fork(); if (!defined($child_pid)) { die "cannot fork: $!\n"; } elsif ($child_pid == 0) { eval { setpgrp() }; chdir($dir); open(STDIN, "<", $stdin) || die "$stdin: $!\n"; open(STDOUT, ">", $stdout) || die "$stdout: $!\n"; open(STDERR, ">", $stderr) || die "$stderr: $!\n"; my($var, $val); $ENV{$var} = $val while (($var, $val) = each %Testie::_variables); $ENV{"rundir"} = ".."; exec $SHELL, "-e", $scriptfn; } else { $running_pid = $child_pid; my($result) = undef; if ($Testie::timeout > 0) { my($before) = Time::HiRes::time(); my($delta) = 10; do { Time::HiRes::usleep($delta); $result = $? if waitpid($child_pid, WNOHANG) > 0; $delta = ($delta < 150000 ? $delta * 2 : 300000); } while (!defined($result) && Time::HiRes::time() < $before + $Testie::timeout); if (!defined($result)) { if (open(X, ">>", out_script_fn_to_fn($stderr))) { print X "testie_failure:timeout\n"; close X; } $result = 124; } $result = 124 if !defined($result); } else { waitpid($child_pid, 0); } $result = $? if !defined($result); # sleep for 1 millisecond to give remaining background jobs a chance # to die select(undef, undef, undef, 0.001); kill('HUP', -$child_pid); # kill any processes left behind $running_pid = 0; $result; } } sub execute_test ($$) { my($tt, $fn) = @_; my($tctr, $teh) = (TestieCounter::new, $tt->eh); ++$tctr->{"test_attempts"}; my($f); # count attempt $tt->{"errprefix"} = $fn . ": "; # print description in superverbose mode if ($::verbose > 1) { return $tctr if $tt->empty; if ($tt->{'info'}) { my($desc) = $tt->{'info'}; $desc =~ s/^(.*?)\t/$1 . (' ' x (8 - (length($1) % 8)))/egm while $desc =~ /\t/; $desc =~ s/\r\n/\n/g; $desc =~ tr/\r/\n/; $desc =~ s/\A\n+//s; $desc =~ s/\n\n.*\z//s; $desc =~ s/^/ /mg; $desc .= "\n" if $desc !~ /\n\z/; $teh->message($fn, " Information:\n", $desc); } $teh->message($fn, " Results:\n"); $tt->{'errprefix'} = " "; } # maybe note that we're running the test if ($::verbose == 1) { $teh->message($tt->{'errprefix'}, "Running...\n"); } elsif ($::verbose == 0) { my($cr_out) = "[" . $tt->{"errprefix"}; $cr_out =~ s/:\s+\z//; $cr_out = "[..." . substr($cr_out, -73) if length($cr_out) > 76; $teh->context($cr_out, "] "); } # check requirements if (exists $tt->{'require'}) { open(SCR, ">", "$dir/\%require") || die "$dir/\%require: $!\n"; print SCR $tt->script_text(\&script_fn_to_fn, 'require'); close SCR; if (!$expand_mode) { my($exitval) = _shell($dir, '%require', '/dev/null', '/dev/null', script_fn_to_fn('stderr')); # if it exited with a bad value, quit if ($exitval) { return $tt->output_error(\&out_script_fn_to_fn, 'require', $tctr); } } } # save the files it names $tt->save_files(\&out_script_fn_to_fn); # save the script open(SCR, ">", "$dir/\%script") || die "$dir/\%script: $!\n"; print SCR $tt->script_text(\&script_fn_to_fn, 'script'); close SCR; # exit if expand mode return $tctr if $expand_mode; # run the script my($actual_stdin) = ($tt->have_file('stdin') ? script_fn_to_fn('stdin') : "/dev/null"); my($actual_stdout) = ($show_stdout || $tt->have_file('stdout') ? script_fn_to_fn('stdout') : "/dev/null"); my($actual_stderr) = script_fn_to_fn('stderr'); my($exitval) = _shell($dir, '%script', $actual_stdin, $actual_stdout, $actual_stderr); # expand "--show-alls" my(@xshow); foreach $f (@show) { if ($f->[0] eq "*") { my(%expanded, @shownit, $k, $v); %expanded = ("stdout" => 1, "stderr" => 1); push @xshow, ["stdout", $f->[1]], ["stderr", $f->[1]]; while (($k, $v) = each %$tt) { next if $k !~ /\A[exv]:(.*)\z/ || exists $expanded{$1}; $expanded{$1} = 1; push @shownit, [$1, $f->[1]]; } push @xshow, sort { $a->[0] cmp $b->[0] } @shownit; } else { push @xshow, $f; } } # echo files foreach $f (@xshow) { $efn = $f->[0]; if (-r out_script_fn_to_fn($efn)) { $teh->showmessage("$fn: ", $efn, "\n", "=" x 79, "\n"); local($/) = undef; open(X, "<", out_script_fn_to_fn($efn)); my($text) = ; close(X); if ($f->[1] && defined($text)) { my($ignores) = Testie::_create_ignores($tt, $efn); if ($ignores) { $text = $ignores->($text); $text =~ s/^\377\n//mg; } } $teh->showmessage($text) if defined $text; $teh->showmessage("=" x 79, "\n"); } elsif ($efn ne "*") { $teh->showmessage("$fn: $efn does not exist\n"); } } if ($exitval) { # if it exited with a bad value, quit $tt->output_error(\&out_script_fn_to_fn, 'script', $tctr); } elsif ($tt->check_expects(\&out_script_fn_to_fn, $tctr)) { # expectsnothing to do } else { # success, print message if verbose if ($::verbose > 0 && !$tt->empty && $tctr->{"errors"} == 0) { $teh->message($tt->{'errprefix'}, "Success!\n"); } } $teh->message("\n") if $::verbose > 1; return $tctr; } sub run_test_read_file ($$) { my($fn, $teh) = @_; # read the testie my($tt, $display_fn, $close_in); if (!defined($fn) || $fn eq '-') { if (!open(IN, "<&=STDIN")) { $teh->message(": $!\n"); return (); } $display_fn = ""; } elsif (-d $fn) { $teh->message($fn, ": is a directory\n"); return (); } else { if (!open(IN, "<", $fn)) { $teh->message($fn, ": $!\n"); return (); } $display_fn = $fn; $close_in = 1; } $tt = Testie::read(IN, $teh, $display_fn); return ($tt, $display_fn, $close_in); } sub run_test_body ($$) { my($fn, $teh) = @_; my($tctr) = TestieCounter::new; my($tt, $display_fn, $close_in) = run_test_read_file($fn, $teh); if (!defined($tt)) { ++$tctr->{"bad_files"}; return $tctr; } my($suffix) = ''; while (1) { my($tctr1) = execute_test($tt, $display_fn . $suffix); if ($tctr1->{"require_errors"}) { ++$tctr->{"test_skips"}; } elsif ($tctr1->{"errors"}) { ++$tctr->{"test_failures"}; } $tctr->add($tctr1); last if !exists $tt->{'continue'}; if (!($suffix =~ s/^<(\d+)>$/"<" . ($1+1) . ">"/e)) { $suffix = "<2>"; } $tt->parse(); } close IN if $close_in; return $tctr; } sub run_test ($$$) { my($fn, $teh, $testnumber) = @_; if (!$::expand_mode) { $dir = "testie$$" . ($testnumber ? "-$testnumber" : ""); if (-d $dir) { $teh->message("warning: $dir directory exists; removing it\n"); system("/bin/rm -rf $dir"); -d $dir && die "cannot remove $dir directory: $!\n"; } mkdir $dir || die "cannot create $dir directory: $!\n"; } my($tctr) = run_test_body($fn, $teh); $teh->complete($tctr); system("/bin/rm -rf $dir") if !$preserve_temporaries; undef $dir; return $tctr; } sub cleanup () { kill("HUP", -$running_pid) if $running_pid; # kill any processes left behind my(@children) = keys %child_pids; foreach my $kid (@children) { kill("HUP", $kid) if $child_pids{$kid}; } system("/bin/rm -rf $dir 2>/dev/null") if defined($dir) && !$preserve_temporaries; } $SIG{'INT'} = $SIG{'QUIT'} = $SIG{'TERM'} = sub { cleanup; exit(1); }; $SIG{'__DIE__'} = \&cleanup; # child processing sub testie_child () { my($p2cr, $p2cw, $c2pr, $c2pw); pipe($p2cr, $p2cw); pipe($c2pr, $c2pw); $p2cw->autoflush(1); defined($c2pr->blocking(0)) || die "cannot set nonblocking: $!"; defined($p2cr->blocking(0)) || die "cannot set nonblocking: $!"; binmode $p2cr; binmode $p2cw; binmode $c2pr; binmode $c2pw; my($child_pid) = fork(); if (!defined($child_pid)) { die "cannot fork: $!\n"; } elsif ($child_pid) { $p2cr->close; $c2pw->close; $child_pids{$child_pid} = 1; return [$c2pr, $p2cw, "", $child_pid]; } $SIG{"CHLD"} = "DEFAULT"; # reset SIG{CHLD} handler from parent eval { setpgrp() }; $::quiet_ebadf = 1; $p2cw->close; $c2pr->close; my($p2crbuf, $testnumber) = ("", 0); my($teh) = TestieChildErrorHandler::new($c2pw); my($command, $arg, $rin, $rout, $win, $wout); $rin = $win = ""; vec($rin, $p2cr->fileno, 1) = 1; vec($win, $c2pw->fileno, 1) = 1; while (1) { if ((($command, $arg) = tipc_read($p2cr, \$p2crbuf))) { if ($command eq "T") { run_test($arg, $teh, $testnumber); ++$testnumber; } elsif ($command eq "X") { exit(0); } else { print STDERR "ipc error: bad command $command\n"; exit(1); } } tipc_error if select($rout = $rin, undef, $wout = $win, undef) < 0; # EPIPE/SIGPIPE to catch dead parent tipc_error if !defined(syswrite($c2pw, " ")); } } sub testie_child_reaper { my $kid; while (($kid = waitpid(-1, WNOHANG)) > 0) { delete $child_pids{$kid}; } } sub testie_parent_loop (\@$$$) { my($tests, $tctr, $teh, $jobs) = @_; my($testpos, $testdone, $rin, $rout) = (0, 0, "", ""); my(@children, @child_out, $command, $arg); $SIG{"CHLD"} = \&testie_child_reaper; for (my $i = 0; $i < $jobs; ++$i) { last if $testpos == @$tests; push @children, testie_child; my($c2pr, $p2cw) = ($children[$i]->[0], $children[$i]->[1]); tipc_write($p2cw, "T", $tests->[$testpos]); ++$testpos; vec($rin, $c2pr->fileno, 1) = 1; push @child_out, []; } while ($testdone < @$tests) { tipc_error if select($rout = $rin, undef, undef, undef) < 0; for (my $i = 0; $i < @children; ++$i) { my($c2pr, $p2cw, $c2prbufref) = ($children[$i]->[0], $children[$i]->[1], \($children[$i]->[2])); while ((($command, $arg) = tipc_read($c2pr, $c2prbufref))) { if ($command eq "C") { $teh->context($arg); } elsif ($command eq "S" || $command eq "E") { push @{$child_out[$i]}, [$command, $arg]; } elsif ($command eq "T") { my($tctr1) = eval($arg); bless $tctr1, TestieCounter; $tctr->add($tctr1); foreach my $x (@{$child_out[$i]}) { if ($x->[0] eq "S") { $teh->showmessage($x->[1]); } else { $teh->message($x->[1]); } } $child_out[$i] = []; ++$testdone; if ($testpos < @$tests) { tipc_write($p2cw, "T", $tests->[$testpos]); ++$testpos; } } else { die "ipc error: bad command $command"; } } } } } # help/usage sub help () { print <<'EOD;'; 'Testie' is a simple test harness. Usage: testie [OPTIONS] [FILE]... Options: VARIABLE=VALUE Variable settings for test script. -V, --verbose Print information for successful tests. -VV, --superverbose Print initial %info for all tests. -s, --show TESTIEFILE Show contents of TESTIEFILE on completion. -S, --show-raw TESTIEFILE Like --show, but include ignored lines. --show-all Show contents of all TESTIEFILEs on completion. --show-all-raw Like --show-all, but include ignored lines. --preserve-temporaries Preserve temporary files. -e, --expand Expand test files into current directory. -p, --path DIR Prepend DIR to PATH. -t, --timeout T Set timeout to T [45 sec]. -v, --version Print version information and exit. --help Print this message and exit. Report bugs and suggestions to . EOD; exit(0); } sub usage () { print STDERR <<'EOD;'; Usage: testie [-V] [FILE]... Try 'testie --help' for more information. EOD; exit(1); } sub print_version () { print <<'EOD;'; Testie 1.3 Copyright (c) 2002-2016 Eddie Kohler Copyright (c) 2002-2003 International Computer Science Institute Copyright (c) 2004-2007 Regents of the University of California Copyright (c) 2008-2010 Meraki, Inc. This is free software; see the source for copying conditions. There is NO warranty, not even for merchantability or fitness for a particular purpose. EOD; exit(0); } sub argcmp ($$$;\$) { my($arg, $opt, $min_match, $store) = @_; $$store = undef if defined($store); return 0 if substr($arg, 0, 2 + $min_match) ne substr($opt, 0, 2 + $min_match); my($eq) = index($arg, '='); my($last) = ($eq >= 0 ? $eq : length($arg)); return 0 if $last > length($opt) || substr($arg, 0, $last) ne substr($opt, 0, $last); return 0 if !defined($store) && $eq >= 0; $$store = substr($arg, $eq + 1) if defined($store) && $eq >= 0; 1; } # directory searching sub search_dir ($$) { my($dir, $aref) = @_; $dir =~ s/\/+$//; if (!opendir(DIR, $dir)) { print STDERR "$dir: $!\n"; return; } my(@f) = grep { !/^\.\.?$/ } readdir(DIR); closedir(DIR); foreach my $f (sort { $a cmp $b } @f) { if (-d "$dir/$f") { &search_dir("$dir/$f", $aref); } elsif ($f =~ /^[^#\.].*\.testie$/) { push @$aref, "$dir/$f"; } } } # argument processing $dir = undef; my(@tests, $arg, $jobs, @pathprepend); $Testie::_variables{"LC_ALL"} = "C"; while (@ARGV) { $_ = shift @ARGV; if (/^([A-Za-z_]\w*)=(.*)$/s) { $Testie::_variables{$1} = $2; } elsif (/^-$/) { push @tests, $_; } elsif (!/^-/) { if (-d $_) { search_dir($_, \@tests); } else { push @tests, $_; } } elsif (/^-v$/ || argcmp($_, '--version', 4)) { print_version; } elsif (/^-q$/ || argcmp($_, '--quiet', 1)) { $::verbose = -1; } elsif (/^-V$/ || argcmp($_, '--verbose', 4)) { $::verbose = 1; } elsif (/^-VV$/ || argcmp($_, '--superverbose', 2)) { $::verbose = 2; } elsif (/^-e$/ || argcmp($_, '--expand', 1)) { $expand_mode = 1; $preserve_temporaries = 1; $dir = "."; } elsif (argcmp($_, '--help', 1)) { help; } elsif (argcmp($_, '--preserve-temporaries', 2) || argcmp($_, '--preserve-temps', 2)) { $preserve_temporaries = 1; } elsif (/^-p$/ || argcmp($_, '--path', 2)) { usage if @ARGV == 0; push @pathprepend, shift @ARGV; } elsif (/^-p(.+)$/) { push @pathprepend, $1; } elsif (argcmp($_, '--path', 2, $arg)) { push @pathprepend, $arg; } elsif (/^-s$/ || argcmp($_, '--show', 2)) { usage if @ARGV == 0; push @show, [(shift @ARGV), 1]; } elsif (/^-s(.+)$/) { push @show, [$1, 1]; } elsif (argcmp($_, '--show', 2, $arg)) { push @show, [$arg, 1]; } elsif (/^-S$/ || argcmp($_, '--show-raw', 6)) { usage if @ARGV == 0; push @show, [(shift @ARGV), 0]; } elsif (/^-S(.+)$/) { push @show, [$1, 0]; } elsif (argcmp($_, '--show-raw', 6, $arg)) { push @show, [$arg, 0]; } elsif (argcmp($_, '--show-all', 6)) { push @show, ["*", 1]; } elsif (argcmp($_, '--show-all-raw', 9)) { push @show, ["*", 0]; } elsif (/^-t$/ || argcmp($_, '--timeout', 1)) { usage if @ARGV == 0; $Testie::timeout = shift @ARGV; } elsif (/^-t(.+)$/) { $Testie::timeout = $1; } elsif (argcmp($_, '--timeout', 1, $arg)) { $Testie::timeout = $arg; } elsif (/^-j$/ || argcmp($_, "--jobs", 1)) { usage if @ARGV == 0 || $ARGV[0] !~ /\A\d+\z/; $jobs = shift @ARGV; } elsif (/^-j(\d+)$/) { $jobs = $1; } elsif (argcmp($_, "--jobs", 1, $arg) && $arg =~ /\A\d+\z/) { $jobs = $arg; } else { usage; } } # prepend to path if (@pathprepend) { my($i, $cwd); chomp($cwd = `pwd`); for ($i = 0; $i != @pathprepend; ++$i) { if ($pathprepend[$i] !~ m{\A/}) { $pathprepend[$i] =~ s{\A\./}{}; $pathprepend[$i] = $cwd . "/" . $pathprepend[$i]; } } $ENV{"PATH"} = join(":", @pathprepend) . ":" . $ENV{"PATH"}; } # check @show for stdout/stderr foreach my $s (@show) { $show_stdout = 1 if $s->[0] eq 'stdout' || $s->[0] eq "*"; $show_stderr = 1 if $s->[0] eq 'stderr' || $s->[0] eq "*"; } push @tests, '-' if !@tests; my($tctr) = TestieCounter::new; my($teh) = TestieErrorHandler::new(@tests > 1 && -t STDERR); if ($jobs && $jobs > 1) { testie_parent_loop(@tests, $tctr, $teh, $jobs); } else { my($testnumber) = 0; foreach my $test (@tests) { my($tctr1) = run_test($test, $teh, $testnumber); $tctr->add($tctr1); ++$testnumber; } } # Print messages about failed requirements @require_error_commands = sort { $a cmp $b } @{$tctr->{"require_error_commands"}}; if (@require_error_commands) { # make list unique for (my $i = 1; $i < @require_error_commands; ) { if ($require_error_commands[$i] eq $require_error_commands[$i - 1]) { splice(@require_error_commands, $i, 1); } else { ++$i; } } $teh->message("testie: requirement failures blocked ", $tctr->{"require_errors"}, ($tctr->{"require_errors"} > 1 ? " tests" : " test"), ", use '-V' for details\n"); $teh->message("testie: (", (@require_error_commands > 1 ? "commands" : "command"), " '", join("', '", @require_error_commands), "')\n"); } my($attempts, $failures, $skips, $successes) = ($tctr->{"test_attempts"}, $tctr->{"test_failures"}, $tctr->{"test_skips"}, $tctr->{"test_attempts"} - $tctr->{"test_failures"} - $tctr->{"test_skips"}); $teh->message("testie: ", $successes, ($successes == 1 ? " success, " : " successes, "), $failures, ($failures == 1 ? " failure, " : " failures, "), $skips, " skipped\n"); if ($tctr->{"bad_files"} > 0) { exit(2); } elsif ($attempts == 0 || ($tctr->{"errors"} == 0 && $skips < $attempts)) { exit(0); } else { exit(1); } =pod =head1 NAME testie - simple test harness =head1 SYNOPSIS testie [OPTIONS] [FILE]... =head1 DESCRIPTION Testie is a simple test harness. A testie test comprises a shell script and, optionally, input and expected output files for that script. Testie runs the script; the test succeeds if all of the script commands succeed, and the actual output files match expectations. Testie accepts test filenames and directories as arguments. Directories are recursively searched for F<*.testie> files. It reports problems for failed tests, plus a summary. Testie exits with status 0 if all tests succeed, 1 if any test fails, and 2 if a test fails due to an internal error. Tests whose B<%require> prerequisites fail do not affect the exit status, except that if all tests' prerequisites fail, the return status is 1 instead of 0. =head1 OPTIONS =over 8 =item B<-j>I, B<--jobs>=I Run up to I tests simultaneously. Like Make's B<-j> option. =item I=I Provide an environment variable setting for I within the script. =item B<-s>, B<--show> I Echo the contents of I on completion. I should be one of the filenames specified by B<%file> or B<%expect>, or B or B. Leaves out any ignored lines. =item B<-S>, B<--show-raw> I Like B<--show>, but includes any ignored lines. =item B<--show-all> Calls B<--show> for all filenames specified by any B<%expect>, plus B and B. Leaves out any ignored lines. =item B<--show-all-raw> Like B<--show-all>, but includes any ignored lines. =item B<-e>, B<--expand> Don't run the given test; instead, expand its files into the current directory. The script is stored in a file called F<%script>. =item B<--preserve-temporaries> Preserve temporary test directories. Testie runs each test in its own subdirectory of the current directory. Test directories are named F, and are typically removed on test completion. Examining the contents of a test directory can be useful when debugging a test. =item B<-p>, B<--path> I Prepend I to the C environment variable before running the test script. =item B<-V>, B<--verbose> Print information to standard error about successful tests as well as unsuccessful tests. =item B<-VV>, B<--superverbose> Like B<--verbose>, but use a slightly different format, and additionally print every test's B<%info> section before the test results. =item B<-q>, B<--quiet> Don't print information to the terminal while running multiple tests. =item B<-v>, B<--version> Print version number information and exit. =item B<--help> Print help information and exit. =back =head1 FILE FORMAT Testie test files consist of several sections, each introduced by a line starting with B<%>. There must be, at least, a B<%script> section. The B<%file> and B<%expect> sections define input and output files by name. =over 8 =item B<%script> The B shell script that controls the test. Testie will run each command in sequence. Every command in the script must succeed, with exit status 0, or the test will fail. Use B<%file> sections to define script input files and B<%expect> sections to check script output files for expected values. The B<%script> section can contain subtests. To start a new subtest, execute a command like S>. Testie will report the problematic C when standard output or error doesn't match an expected value. The script's environment is populated with any Is set on the testie command line with B=I> syntax. Also, the B<$rundir> environment variable is set to the directory in which testie was originally run. =item B<%require [-q]> An B shell script defining prerequisites that must be satisfied before the test can run. Every command in the script must succeed, with exit status 0, for the test to run. Standard output and error are not checked, however. The B<-q> flag tells testie not to print an error message if a requirement fails. Testie runs the requirement script before creating any other test files. For example, contents of B<%file> sections are not available. =item B<%info> A short description of the test. In B<--superverbose> mode, the first paragraph of its contents is printed before the test results. =item B<%cut> This section is ignored. It is intended to comment out obsolete parts of the test. =item B<%file [-de] [+I] I...> Create an input file for the script. I can be B, which sets the script's standard input. If B<+>I is provided, the file data consists of the I bytes following this line; otherwise, it consists of the data up to the next section. The B<-d> flag tells testie to delete the first character of each line in the section. The B<-e> flag indicates that the section was MIME Base64-encoded (see L); it is decoded before use. To include a file with lines that start with B<%> (which would normally start a new section), use B<-d> and preface each line of the file with a space, or use B<-e>. =item B<%expect [-adeiw] [+I] I...> Define an expected output file. Differences between the script's output I and the contents of the B<%expect> section will cause the test to fail. I can be B, for standard output. If B<+>I is provided, the file data consists of the I bytes following this line; otherwise, it consists of the data up to the next section. After running the script, testie compares the I generated by the script with the provided data. The files are compared line-by-line. Testie ignores blank lines, differences in trailing whitespace, and lines in the script output that match B<%ignore> patterns (see below). The B<-w> flag causes testie to ignore differences in amount of whitespace within each line. B<%expect> lines can contain Perl regular expressions, enclosed by two sets of braces. The B<%expect> line foo{{(bar)?}} matches either C or C. The B<-i> flag makes all such regular expressions case-insensitive. (Text outside of regular expressions must match case.) Document an B<%expect> line with C<{{?comment}}> blocks. For example: foo {{? the sort was in the right order}} Testie ignores whitespace before and after the C<{{?comment}}> block, and if the actual output differs from this expected line, it prints the comment in addition to the line differences. The B<-a> flag marks this expected output as an alternate. Testie will compare the script's output file with each provided alternate; the test succeeds if any of the alternates match. The B<-d> flag behaves as in B<%file>. =item B<%expectv [-ade] [+I] I...> Define a literal expected output file. This behaves like B<%expect>, except that the script's output file must match the provided data I: B<%expectv> never ignores whitespace differences, does not treat C<{{}}> blocks as regular expressions, and does not parse B<%ignore> patterns. =item B<%expectx [-adiw] [+I] I...> Define a regular-expression expected output file. This behaves like B<%expect>, except that every line is treated as a regular expression. C<{{?comment}}> blocks are ignored, but other brace pairs are treated according to the normal regular expression rules. =item B<%stdin [-de] [+I]> Same as B<%file stdin>. =item B<%stdout [-adeiw] [+I]> Same as B<%expect stdout>. =item B<%stderr [-adeiw] [+I]> Same as B<%expect stderr>. =item B<%ignorex [-di] [+I] [I]> Each line in the B<%ignorex> section is a Perl regular expression. Lines in the supplied I that match any of those regular expressions will not be considered when comparing files with B<%expect> data. The regular expression must match the whole line. I may be B, in which case the regular expressions will apply to all B<%expect> files. C<{{?comment}}> blocks are ignored. =item B<%ignore>, B<%ignorev [-adeiw] [+I] [I]> Like B<%ignorex>, but B<%ignore> parses regular expressions only inside double braces (C<{{ }}>), and B<%ignorev> lines must match exactly. =item B<%include I> Interpolate the contents of another testie file. =item B<%eot> Marks the end of the current test. The rest of the file will be parsed for additional tests. =item B<%eof> The rest of the file is ignored. =back =head1 EXAMPLE This simple testie script checks that 'grep -c' works for a simple output file. %script grep -c B. %stdin Bfoo B %stdout 1 =head1 ENVIRONMENT By default, testie sets the C environment variable to "C"; without this setting commands like B have unpredictable effects. To set C to another value, set it in the B<%script> section. =head1 AUTHOR Eddie Kohler,