diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f288702
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ 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 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/Qt_Gif_Creator.pro b/Qt_Gif_Creator.pro
new file mode 100644
index 0000000..17eb3fe
--- /dev/null
+++ b/Qt_Gif_Creator.pro
@@ -0,0 +1,60 @@
+#-------------------------------------------------
+#
+# Project created by QtCreator 2019-05-03T17:36:28
+#
+#-------------------------------------------------
+
+QT += core gui
+
+greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
+
+TARGET = Qt_Gif_Creator
+TEMPLATE = app
+
+# The following define makes your compiler emit warnings if you use
+# any feature of Qt which has been marked as deprecated (the exact warnings
+# depend on your compiler). Please consult the documentation of the
+# deprecated API in order to know how to port your code away from it.
+DEFINES += QT_DEPRECATED_WARNINGS
+
+# You can also make your code fail to compile if you use deprecated APIs.
+# In order to do so, uncomment the following line.
+# You can also select to disable deprecated APIs only up to a certain version of Qt.
+#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
+
+CONFIG += c++11
+
+SOURCES += \
+ main.cpp \
+ xygifcreator.cpp \
+ xygifframe.cpp \
+ xymovablewidget.cpp \
+ xypackimage.cpp
+
+HEADERS += \
+ xygifcreator.h \
+ xygifframe.h \
+ xymovablewidget.h \
+ xypackimage.h \
+ gif.h
+
+FORMS += \
+ xygifframe.ui \
+ xypackimage.ui
+
+# Default rules for deployment.
+qnx: target.path = /tmp/$${TARGET}/bin
+else: unix:!android: target.path = /opt/$${TARGET}/bin
+!isEmpty(target.path): INSTALLS += target
+
+RESOURCES += \
+ images.qrc
+
+win32 {
+LIBS += -lGdi32 -lUser32
+msvc {
+greaterThan(QT_MAJOR_VERSION, 4): RC_FILE = ico.rc
+}
+} else {
+LIBS += -lX11 -lXfixes
+}
diff --git a/Qt_Gif_Creator.pro.user b/Qt_Gif_Creator.pro.user
new file mode 100644
index 0000000..a18aa31
--- /dev/null
+++ b/Qt_Gif_Creator.pro.user
@@ -0,0 +1,319 @@
+
+
+
+
+
+ EnvironmentId
+ {69e4269a-5394-41be-8a9b-90e750e40573}
+
+
+ ProjectExplorer.Project.ActiveTarget
+ 0
+
+
+ ProjectExplorer.Project.EditorSettings
+
+ true
+ false
+ true
+
+ Cpp
+
+ CppGlobal
+
+
+
+ QmlJS
+
+ QmlJSGlobal
+
+
+ 2
+ GBK
+ false
+ 4
+ false
+ 80
+ true
+ true
+ 1
+ true
+ false
+ 0
+ true
+ true
+ 0
+ 8
+ true
+ 0
+ true
+ true
+ true
+ *.md, *.MD, Makefile
+ false
+ true
+
+
+
+ ProjectExplorer.Project.PluginSettings
+
+
+ true
+ true
+ true
+ true
+ true
+
+
+ 0
+ true
+
+ -fno-delayed-template-parsing
+
+ true
+ Builtin.Questionable
+
+ true
+ true
+ Builtin.DefaultTidyAndClazy
+ 3
+
+
+
+ true
+
+
+
+
+ ProjectExplorer.Project.Target.0
+
+ Desktop
+ Desktop Qt 5.15.2 MSVC2015 64bit
+ Desktop Qt 5.15.2 MSVC2015 64bit
+ qt.qt5.5152.win64_msvc2015_64_kit
+ 0
+ 0
+ 0
+
+ 0
+ D:\build-Qt_Gif_Creator-Desktop_Qt_5_15_2_MSVC2015_64bit-Debug
+ D:/build-Qt_Gif_Creator-Desktop_Qt_5_15_2_MSVC2015_64bit-Debug
+
+
+ true
+ QtProjectManager.QMakeBuildStep
+
+ false
+
+
+
+ true
+ Qt4ProjectManager.MakeStep
+
+ 2
+ Build
+ Build
+ ProjectExplorer.BuildSteps.Build
+
+
+
+ true
+ Qt4ProjectManager.MakeStep
+ clean
+
+ 1
+ Clean
+ Clean
+ ProjectExplorer.BuildSteps.Clean
+
+ 2
+ false
+
+
+ Debug
+ Qt4ProjectManager.Qt4BuildConfiguration
+ 2
+ 0
+
+
+ D:\build-Qt_Gif_Creator-Desktop_Qt_5_15_2_MSVC2015_64bit-Release
+ D:/build-Qt_Gif_Creator-Desktop_Qt_5_15_2_MSVC2015_64bit-Release
+
+
+ true
+ QtProjectManager.QMakeBuildStep
+
+ false
+
+
+
+ true
+ Qt4ProjectManager.MakeStep
+
+ 2
+ Build
+ Build
+ ProjectExplorer.BuildSteps.Build
+
+
+
+ true
+ Qt4ProjectManager.MakeStep
+ clean
+
+ 1
+ Clean
+ Clean
+ ProjectExplorer.BuildSteps.Clean
+
+ 2
+ false
+
+
+ Release
+ Qt4ProjectManager.Qt4BuildConfiguration
+ 0
+ 0
+ 0
+
+
+ 0
+ D:\build-Qt_Gif_Creator-Desktop_Qt_5_15_2_MSVC2015_64bit-Profile
+ D:/build-Qt_Gif_Creator-Desktop_Qt_5_15_2_MSVC2015_64bit-Profile
+
+
+ true
+ QtProjectManager.QMakeBuildStep
+
+ false
+
+
+
+ true
+ Qt4ProjectManager.MakeStep
+
+ 2
+ Build
+ Build
+ ProjectExplorer.BuildSteps.Build
+
+
+
+ true
+ Qt4ProjectManager.MakeStep
+ clean
+
+ 1
+ Clean
+ Clean
+ ProjectExplorer.BuildSteps.Clean
+
+ 2
+ false
+
+
+ Profile
+ Qt4ProjectManager.Qt4BuildConfiguration
+ 0
+ 0
+ 0
+ 0
+
+ 3
+
+
+ 0
+ Deploy
+ Deploy
+ ProjectExplorer.BuildSteps.Deploy
+
+ 1
+
+ false
+ ProjectExplorer.DefaultDeployConfiguration
+
+ 1
+
+ dwarf
+
+ cpu-cycles
+
+
+ 250
+
+ -e
+ cpu-cycles
+ --call-graph
+ dwarf,4096
+ -F
+ 250
+
+ -F
+ true
+ 4096
+ false
+ false
+ 1000
+
+ true
+
+ false
+ false
+ false
+ false
+ true
+ 0.01
+ 10
+ true
+ kcachegrind
+ 1
+ 25
+
+ 1
+ true
+ false
+ true
+ valgrind
+
+ 0
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+
+
+ 2
+
+ ProjectExplorer.CustomExecutableRunConfiguration
+
+ false
+ true
+ false
+ true
+
+ 1
+
+
+
+ ProjectExplorer.Project.TargetCount
+ 1
+
+
+ ProjectExplorer.Project.Updater.FileVersion
+ 22
+
+
+ Version
+ 22
+
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..56ca13b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,6 @@
+# Qt_Gif_Creator
+基于Qt的屏幕gif录制工具!
+
+可以体验可执行包(windows版本):https://pan.baidu.com/s/1tvi8h9zp7Ws3-yIBsSwKkw 提取码:17a2
+
+
diff --git a/gif.h b/gif.h
new file mode 100644
index 0000000..72cc148
--- /dev/null
+++ b/gif.h
@@ -0,0 +1,835 @@
+//
+// gif.h
+// by Charlie Tangora
+// Public domain.
+// Email me : ctangora -at- gmail -dot- com
+//
+// This file offers a simple, very limited way to create animated GIFs directly in code.
+//
+// Those looking for particular cleverness are likely to be disappointed; it's pretty
+// much a straight-ahead implementation of the GIF format with optional Floyd-Steinberg
+// dithering. (It does at least use delta encoding - only the changed portions of each
+// frame are saved.)
+//
+// So resulting files are often quite large. The hope is that it will be handy nonetheless
+// as a quick and easily-integrated way for programs to spit out animations.
+//
+// Only RGBA8 is currently supported as an input format. (The alpha is ignored.)
+//
+// If capturing a buffer with a bottom-left origin (such as OpenGL), define GIF_FLIP_VERT
+// to automatically flip the buffer data when writing the image (the buffer itself is
+// unchanged.
+//
+// USAGE:
+// Create a GifWriter struct. Pass it to GifBegin() to initialize and write the header.
+// Pass subsequent frames to GifWriteFrame().
+// Finally, call GifEnd() to close the file handle and free memory.
+//
+
+#ifndef gif_h
+#define gif_h
+
+#include // for FILE*
+#include // for memcpy and bzero
+#include // for integer typedefs
+
+// Define these macros to hook into a custom memory allocator.
+// TEMP_MALLOC and TEMP_FREE will only be called in stack fashion - frees in the reverse order of mallocs
+// and any temp memory allocated by a function will be freed before it exits.
+// MALLOC and FREE are used only by GifBegin and GifEnd respectively (to allocate a buffer the size of the image, which
+// is used to find changed pixels for delta-encoding.)
+
+#ifndef GIF_TEMP_MALLOC
+#include
+#define GIF_TEMP_MALLOC malloc
+#endif
+
+#ifndef GIF_TEMP_FREE
+#include
+#define GIF_TEMP_FREE free
+#endif
+
+#ifndef GIF_MALLOC
+#include
+#define GIF_MALLOC malloc
+#endif
+
+#ifndef GIF_FREE
+#include
+#define GIF_FREE free
+#endif
+
+const int kGifTransIndex = 0;
+
+struct GifPalette
+{
+ int bitDepth;
+
+ uint8_t r[256];
+ uint8_t g[256];
+ uint8_t b[256];
+
+ // k-d tree over RGB space, organized in heap fashion
+ // i.e. left child of node i is node i*2, right child is node i*2+1
+ // nodes 256-511 are implicitly the leaves, containing a color
+ uint8_t treeSplitElt[255];
+ uint8_t treeSplit[255];
+};
+
+// max, min, and abs functions
+int GifIMax(int l, int r) { return l>r?l:r; }
+int GifIMin(int l, int r) { return l (1<bitDepth)-1)
+ {
+ int ind = treeRoot-(1<bitDepth);
+ if(ind == kGifTransIndex) return;
+
+ // check whether this color is better than the current winner
+ int r_err = r - ((int32_t)pPal->r[ind]);
+ int g_err = g - ((int32_t)pPal->g[ind]);
+ int b_err = b - ((int32_t)pPal->b[ind]);
+ int diff = GifIAbs(r_err)+GifIAbs(g_err)+GifIAbs(b_err);
+
+ if(diff < bestDiff)
+ {
+ bestInd = ind;
+ bestDiff = diff;
+ }
+
+ return;
+ }
+
+ // take the appropriate color (r, g, or b) for this node of the k-d tree
+ int comps[3]; comps[0] = r; comps[1] = g; comps[2] = b;
+ int splitComp = comps[pPal->treeSplitElt[treeRoot]];
+
+ int splitPos = pPal->treeSplit[treeRoot];
+ if(splitPos > splitComp)
+ {
+ // check the left subtree
+ GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot*2);
+ if( bestDiff > splitPos - splitComp )
+ {
+ // cannot prove there's not a better value in the right subtree, check that too
+ GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot*2+1);
+ }
+ }
+ else
+ {
+ GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot*2+1);
+ if( bestDiff > splitComp - splitPos )
+ {
+ GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot*2);
+ }
+ }
+}
+
+void GifSwapPixels(uint8_t* image, int pixA, int pixB)
+{
+ uint8_t rA = image[pixA*4];
+ uint8_t gA = image[pixA*4+1];
+ uint8_t bA = image[pixA*4+2];
+ uint8_t aA = image[pixA*4+3];
+
+ uint8_t rB = image[pixB*4];
+ uint8_t gB = image[pixB*4+1];
+ uint8_t bB = image[pixB*4+2];
+ uint8_t aB = image[pixA*4+3];
+
+ image[pixA*4] = rB;
+ image[pixA*4+1] = gB;
+ image[pixA*4+2] = bB;
+ image[pixA*4+3] = aB;
+
+ image[pixB*4] = rA;
+ image[pixB*4+1] = gA;
+ image[pixB*4+2] = bA;
+ image[pixB*4+3] = aA;
+}
+
+// just the partition operation from quicksort
+int GifPartition(uint8_t* image, const int left, const int right, const int elt, int pivotIndex)
+{
+ const int pivotValue = image[(pivotIndex)*4+elt];
+ GifSwapPixels(image, pivotIndex, right-1);
+ int storeIndex = left;
+ bool split = 0;
+ for(int ii=left; ii neededCenter)
+ GifPartitionByMedian(image, left, pivotIndex, com, neededCenter);
+
+ if(pivotIndex < neededCenter)
+ GifPartitionByMedian(image, pivotIndex+1, right, com, neededCenter);
+ }
+}
+
+// Builds a palette by creating a balanced k-d tree of all pixels in the image
+void GifSplitPalette(uint8_t* image, int numPixels, int firstElt, int lastElt, int splitElt, int splitDist, int treeNode, bool buildForDither, GifPalette* pal)
+{
+ if(lastElt <= firstElt || numPixels == 0)
+ return;
+
+ // base case, bottom of the tree
+ if(lastElt == firstElt+1)
+ {
+ if(buildForDither)
+ {
+ // Dithering needs at least one color as dark as anything
+ // in the image and at least one brightest color -
+ // otherwise it builds up error and produces strange artifacts
+ if( firstElt == 1 )
+ {
+ // special case: the darkest color in the image
+ uint32_t r=255, g=255, b=255;
+ for(int ii=0; iir[firstElt] = (uint8_t)r;
+ pal->g[firstElt] = (uint8_t)g;
+ pal->b[firstElt] = (uint8_t)b;
+
+ return;
+ }
+
+ if( firstElt == (1 << pal->bitDepth)-1 )
+ {
+ // special case: the lightest color in the image
+ uint32_t r=0, g=0, b=0;
+ for(int ii=0; iir[firstElt] = (uint8_t)r;
+ pal->g[firstElt] = (uint8_t)g;
+ pal->b[firstElt] = (uint8_t)b;
+
+ return;
+ }
+ }
+
+ // otherwise, take the average of all colors in this subcube
+ uint64_t r=0, g=0, b=0;
+ for(int ii=0; iir[firstElt] = (uint8_t)r;
+ pal->g[firstElt] = (uint8_t)g;
+ pal->b[firstElt] = (uint8_t)b;
+
+ return;
+ }
+
+ // Find the axis with the largest range
+ int minR = 255, maxR = 0;
+ int minG = 255, maxG = 0;
+ int minB = 255, maxB = 0;
+ for(int ii=0; ii maxR) maxR = r;
+ if(r < minR) minR = r;
+
+ if(g > maxG) maxG = g;
+ if(g < minG) minG = g;
+
+ if(b > maxB) maxB = b;
+ if(b < minB) minB = b;
+ }
+
+ int rRange = maxR - minR;
+ int gRange = maxG - minG;
+ int bRange = maxB - minB;
+
+ // and split along that axis. (incidentally, this means this isn't a "proper" k-d tree but I don't know what else to call it)
+ int splitCom = 1;
+ if(bRange > gRange) splitCom = 2;
+ if(rRange > bRange && rRange > gRange) splitCom = 0;
+
+ int subPixelsA = numPixels * (splitElt - firstElt) / (lastElt - firstElt);
+ int subPixelsB = numPixels-subPixelsA;
+
+ GifPartitionByMedian(image, 0, numPixels, splitCom, subPixelsA);
+
+ pal->treeSplitElt[treeNode] = (uint8_t)splitCom;
+ pal->treeSplit[treeNode] = image[subPixelsA*4+splitCom];
+
+ GifSplitPalette(image, subPixelsA, firstElt, splitElt, splitElt-splitDist, splitDist/2, treeNode*2, buildForDither, pal);
+ GifSplitPalette(image+subPixelsA*4, subPixelsB, splitElt, lastElt, splitElt+splitDist, splitDist/2, treeNode*2+1, buildForDither, pal);
+}
+
+// Finds all pixels that have changed from the previous image and
+// moves them to the fromt of th buffer.
+// This allows us to build a palette optimized for the colors of the
+// changed pixels only.
+int GifPickChangedPixels( const uint8_t* lastFrame, uint8_t* frame, int numPixels )
+{
+ int numChanged = 0;
+ uint8_t* writeIter = frame;
+
+ for (int ii=0; iibitDepth = bitDepth;
+
+ // SplitPalette is destructive (it sorts the pixels by color) so
+ // we must create a copy of the image for it to destroy
+ size_t imageSize = (size_t)(width * height * 4 * sizeof(uint8_t));
+ uint8_t* destroyableImage = (uint8_t*)GIF_TEMP_MALLOC(imageSize);
+ memcpy(destroyableImage, nextFrame, imageSize);
+
+ int numPixels = (int)(width * height);
+ if(lastFrame)
+ numPixels = GifPickChangedPixels(lastFrame, destroyableImage, numPixels);
+
+ const int lastElt = 1 << bitDepth;
+ const int splitElt = lastElt/2;
+ const int splitDist = splitElt/2;
+
+ GifSplitPalette(destroyableImage, numPixels, 1, lastElt, splitElt, splitDist, 1, buildForDither, pPal);
+
+ GIF_TEMP_FREE(destroyableImage);
+
+ // add the bottom node for the transparency index
+ pPal->treeSplit[1 << (bitDepth-1)] = 0;
+ pPal->treeSplitElt[1 << (bitDepth-1)] = 0;
+
+ pPal->r[0] = pPal->g[0] = pPal->b[0] = 0;
+}
+
+// Implements Floyd-Steinberg dithering, writes palette value to alpha
+void GifDitherImage( const uint8_t* lastFrame, const uint8_t* nextFrame, uint8_t* outFrame, uint32_t width, uint32_t height, GifPalette* pPal )
+{
+ int numPixels = (int)(width * height);
+
+ // quantPixels initially holds color*256 for all pixels
+ // The extra 8 bits of precision allow for sub-single-color error values
+ // to be propagated
+ int32_t *quantPixels = (int32_t *)GIF_TEMP_MALLOC(sizeof(int32_t) * (size_t)numPixels * 4);
+
+ for( int ii=0; iir[bestInd]) * 256;
+ int32_t g_err = nextPix[1] - int32_t(pPal->g[bestInd]) * 256;
+ int32_t b_err = nextPix[2] - int32_t(pPal->b[bestInd]) * 256;
+
+ nextPix[0] = pPal->r[bestInd];
+ nextPix[1] = pPal->g[bestInd];
+ nextPix[2] = pPal->b[bestInd];
+ nextPix[3] = bestInd;
+
+ // Propagate the error to the four adjacent locations
+ // that we haven't touched yet
+ int quantloc_7 = (int)(yy * width + xx + 1);
+ int quantloc_3 = (int)(yy * width + width + xx - 1);
+ int quantloc_5 = (int)(yy * width + width + xx);
+ int quantloc_1 = (int)(yy * width + width + xx + 1);
+
+ if(quantloc_7 < numPixels)
+ {
+ int32_t* pix7 = quantPixels+4*quantloc_7;
+ pix7[0] += GifIMax( -pix7[0], r_err * 7 / 16 );
+ pix7[1] += GifIMax( -pix7[1], g_err * 7 / 16 );
+ pix7[2] += GifIMax( -pix7[2], b_err * 7 / 16 );
+ }
+
+ if(quantloc_3 < numPixels)
+ {
+ int32_t* pix3 = quantPixels+4*quantloc_3;
+ pix3[0] += GifIMax( -pix3[0], r_err * 3 / 16 );
+ pix3[1] += GifIMax( -pix3[1], g_err * 3 / 16 );
+ pix3[2] += GifIMax( -pix3[2], b_err * 3 / 16 );
+ }
+
+ if(quantloc_5 < numPixels)
+ {
+ int32_t* pix5 = quantPixels+4*quantloc_5;
+ pix5[0] += GifIMax( -pix5[0], r_err * 5 / 16 );
+ pix5[1] += GifIMax( -pix5[1], g_err * 5 / 16 );
+ pix5[2] += GifIMax( -pix5[2], b_err * 5 / 16 );
+ }
+
+ if(quantloc_1 < numPixels)
+ {
+ int32_t* pix1 = quantPixels+4*quantloc_1;
+ pix1[0] += GifIMax( -pix1[0], r_err / 16 );
+ pix1[1] += GifIMax( -pix1[1], g_err / 16 );
+ pix1[2] += GifIMax( -pix1[2], b_err / 16 );
+ }
+ }
+ }
+
+ // Copy the palettized result to the output buffer
+ for( int ii=0; iir[bestInd];
+ outFrame[1] = pPal->g[bestInd];
+ outFrame[2] = pPal->b[bestInd];
+ outFrame[3] = (uint8_t)bestInd;
+ }
+
+ if(lastFrame) lastFrame += 4;
+ outFrame += 4;
+ nextFrame += 4;
+ }
+}
+
+// Simple structure to write out the LZW-compressed portion of the image
+// one bit at a time
+struct GifBitStatus
+{
+ uint8_t bitIndex; // how many bits in the partial byte written so far
+ uint8_t byte; // current partial byte
+
+ uint32_t chunkIndex;
+ uint8_t chunk[256]; // bytes are written in here until we have 256 of them, then written to the file
+};
+
+// insert a single bit
+void GifWriteBit( GifBitStatus& stat, uint32_t bit )
+{
+ bit = bit & 1;
+ bit = bit << stat.bitIndex;
+ stat.byte |= bit;
+
+ ++stat.bitIndex;
+ if( stat.bitIndex > 7 )
+ {
+ // move the newly-finished byte to the chunk buffer
+ stat.chunk[stat.chunkIndex++] = stat.byte;
+ // and start a new byte
+ stat.bitIndex = 0;
+ stat.byte = 0;
+ }
+}
+
+// write all bytes so far to the file
+void GifWriteChunk( FILE* f, GifBitStatus& stat )
+{
+ fputc((int)stat.chunkIndex, f);
+ fwrite(stat.chunk, 1, stat.chunkIndex, f);
+
+ stat.bitIndex = 0;
+ stat.byte = 0;
+ stat.chunkIndex = 0;
+}
+
+void GifWriteCode( FILE* f, GifBitStatus& stat, uint32_t code, uint32_t length )
+{
+ for( uint32_t ii=0; ii> 1;
+
+ if( stat.chunkIndex == 255 )
+ {
+ GifWriteChunk(f, stat);
+ }
+ }
+}
+
+// The LZW dictionary is a 256-ary tree constructed as the file is encoded,
+// this is one node
+struct GifLzwNode
+{
+ uint16_t m_next[256];
+};
+
+// write a 256-color (8-bit) image palette to the file
+void GifWritePalette( const GifPalette* pPal, FILE* f )
+{
+ fputc(0, f); // first color: transparency
+ fputc(0, f);
+ fputc(0, f);
+
+ for(int ii=1; ii<(1 << pPal->bitDepth); ++ii)
+ {
+ uint32_t r = pPal->r[ii];
+ uint32_t g = pPal->g[ii];
+ uint32_t b = pPal->b[ii];
+
+ fputc((int)r, f);
+ fputc((int)g, f);
+ fputc((int)b, f);
+ }
+}
+
+// write the image header, LZW-compress and write out the image
+void GifWriteLzwImage(FILE* f, uint8_t* image, uint32_t left, uint32_t top, uint32_t width, uint32_t height, uint32_t delay, GifPalette* pPal)
+{
+ // graphics control extension
+ fputc(0x21, f);
+ fputc(0xf9, f);
+ fputc(0x04, f);
+ fputc(0x05, f); // leave prev frame in place, this frame has transparency
+ fputc(delay & 0xff, f);
+ fputc((delay >> 8) & 0xff, f);
+ fputc(kGifTransIndex, f); // transparent color index
+ fputc(0, f);
+
+ fputc(0x2c, f); // image descriptor block
+
+ fputc(left & 0xff, f); // corner of image in canvas space
+ fputc((left >> 8) & 0xff, f);
+ fputc(top & 0xff, f);
+ fputc((top >> 8) & 0xff, f);
+
+ fputc(width & 0xff, f); // width and height of image
+ fputc((width >> 8) & 0xff, f);
+ fputc(height & 0xff, f);
+ fputc((height >> 8) & 0xff, f);
+
+ //fputc(0, f); // no local color table, no transparency
+ //fputc(0x80, f); // no local color table, but transparency
+
+ fputc(0x80 + pPal->bitDepth-1, f); // local color table present, 2 ^ bitDepth entries
+ GifWritePalette(pPal, f);
+
+ const int minCodeSize = pPal->bitDepth;
+ const uint32_t clearCode = 1 << pPal->bitDepth;
+
+ fputc(minCodeSize, f); // min code size 8 bits
+
+ GifLzwNode* codetree = (GifLzwNode*)GIF_TEMP_MALLOC(sizeof(GifLzwNode)*4096);
+
+ memset(codetree, 0, sizeof(GifLzwNode)*4096);
+ int32_t curCode = -1;
+ uint32_t codeSize = (uint32_t)minCodeSize + 1;
+ uint32_t maxCode = clearCode+1;
+
+ GifBitStatus stat;
+ stat.byte = 0;
+ stat.bitIndex = 0;
+ stat.chunkIndex = 0;
+
+ GifWriteCode(f, stat, clearCode, codeSize); // start with a fresh LZW dictionary
+
+ for(uint32_t yy=0; yy= (1ul << codeSize) )
+ {
+ // dictionary entry count has broken a size barrier,
+ // we need more bits for codes
+ codeSize++;
+ }
+ if( maxCode == 4095 )
+ {
+ // the dictionary is full, clear it out and begin anew
+ GifWriteCode(f, stat, clearCode, codeSize); // clear tree
+
+ memset(codetree, 0, sizeof(GifLzwNode)*4096);
+ codeSize = (uint32_t)(minCodeSize + 1);
+ maxCode = clearCode+1;
+ }
+
+ curCode = nextValue;
+ }
+ }
+ }
+
+ // compression footer
+ GifWriteCode(f, stat, (uint32_t)curCode, codeSize);
+ GifWriteCode(f, stat, clearCode, codeSize);
+ GifWriteCode(f, stat, clearCode + 1, (uint32_t)minCodeSize + 1);
+
+ // write out the last partial chunk
+ while( stat.bitIndex ) GifWriteBit(stat, 0);
+ if( stat.chunkIndex ) GifWriteChunk(f, stat);
+
+ fputc(0, f); // image block terminator
+
+ GIF_TEMP_FREE(codetree);
+}
+
+struct GifWriter
+{
+ FILE* f;
+ uint8_t* oldImage;
+ bool firstFrame;
+};
+
+// Creates a gif file.
+// The input GIFWriter is assumed to be uninitialized.
+// The delay value is the time between frames in hundredths of a second - note that not all viewers pay much attention to this value.
+bool GifBegin( GifWriter* writer, const char* filename, uint32_t width, uint32_t height, uint32_t delay, int32_t bitDepth = 8, bool dither = false )
+{
+ (void)bitDepth; (void)dither; // Mute "Unused argument" warnings
+#if defined(_MSC_VER) && (_MSC_VER >= 1400)
+ writer->f = 0;
+ fopen_s(&writer->f, filename, "wb");
+#else
+ writer->f = fopen(filename, "wb");
+#endif
+ if(!writer->f) return false;
+
+ writer->firstFrame = true;
+
+ // allocate
+ writer->oldImage = (uint8_t*)GIF_MALLOC(width*height*4);
+
+ fputs("GIF89a", writer->f);
+
+ // screen descriptor
+ fputc(width & 0xff, writer->f);
+ fputc((width >> 8) & 0xff, writer->f);
+ fputc(height & 0xff, writer->f);
+ fputc((height >> 8) & 0xff, writer->f);
+
+ fputc(0xf0, writer->f); // there is an unsorted global color table of 2 entries
+ fputc(0, writer->f); // background color
+ fputc(0, writer->f); // pixels are square (we need to specify this because it's 1989)
+
+ // now the "global" palette (really just a dummy palette)
+ // color 0: black
+ fputc(0, writer->f);
+ fputc(0, writer->f);
+ fputc(0, writer->f);
+ // color 1: also black
+ fputc(0, writer->f);
+ fputc(0, writer->f);
+ fputc(0, writer->f);
+
+ if( delay != 0 )
+ {
+ // animation header
+ fputc(0x21, writer->f); // extension
+ fputc(0xff, writer->f); // application specific
+ fputc(11, writer->f); // length 11
+ fputs("NETSCAPE2.0", writer->f); // yes, really
+ fputc(3, writer->f); // 3 bytes of NETSCAPE2.0 data
+
+ fputc(1, writer->f); // JUST BECAUSE
+ fputc(0, writer->f); // loop infinitely (byte 0)
+ fputc(0, writer->f); // loop infinitely (byte 1)
+
+ fputc(0, writer->f); // block terminator
+ }
+
+ return true;
+}
+
+// Writes out a new frame to a GIF in progress.
+// The GIFWriter should have been created by GIFBegin.
+// AFAIK, it is legal to use different bit depths for different frames of an image -
+// this may be handy to save bits in animations that don't change much.
+bool GifWriteFrame( GifWriter* writer, const uint8_t* image, uint32_t width, uint32_t height, uint32_t delay, int bitDepth = 8, bool dither = false )
+{
+ if(!writer->f) return false;
+
+ const uint8_t* oldImage = writer->firstFrame? NULL : writer->oldImage;
+ writer->firstFrame = false;
+
+ GifPalette pal;
+ GifMakePalette((dither? NULL : oldImage), image, width, height, bitDepth, dither, &pal);
+
+ if(dither)
+ GifDitherImage(oldImage, image, writer->oldImage, width, height, &pal);
+ else
+ GifThresholdImage(oldImage, image, writer->oldImage, width, height, &pal);
+
+ GifWriteLzwImage(writer->f, writer->oldImage, 0, 0, width, height, delay, &pal);
+
+ return true;
+}
+
+// Writes the EOF code, closes the file handle, and frees temp memory used by a GIF.
+// Many if not most viewers will still display a GIF properly if the EOF code is missing,
+// but it's still a good idea to write it out.
+bool GifEnd( GifWriter* writer )
+{
+ if(!writer->f) return false;
+
+ fputc(0x3b, writer->f); // end of file
+ fclose(writer->f);
+ GIF_FREE(writer->oldImage);
+
+ writer->f = NULL;
+ writer->oldImage = NULL;
+
+ return true;
+}
+
+#endif
diff --git a/gif.ico b/gif.ico
new file mode 100644
index 0000000..4c9826a
Binary files /dev/null and b/gif.ico differ
diff --git a/ico.rc b/ico.rc
new file mode 100644
index 0000000..7721f55
--- /dev/null
+++ b/ico.rc
@@ -0,0 +1,58 @@
+#include "winres.h"
+
+#define MAJOR 1
+#define MINOR 0
+#define PATCH 0
+
+#define VERSION_STR "1.0.0.0"
+
+#define PRODUCT_ICON "gif.ico" // ͼ
+
+#define FILE_VERSION MAJOR,MINOR,PATCH // ļ汾
+#define FILE_VERSION_STR VERSION_STR
+#define PRODUCT_VERSION FILE_VERSION // Ʒ汾
+#define PRODUCT_VERSION_STR FILE_VERSION_STR
+#define COMPANY_NAME "xiaoyanLG"
+#define INTERNAL_NAME "Qt_Gif_Creator.exe"
+#define FILE_DESCRIPTION "Ļ¼ƹ" // ļ˵
+#define LEGAL_COPYRIGHT "ֹҵ;(QQ 624080979)" // Ȩ
+#define ORIGINAL_FILE_NAME "Qt_Gif_Creator.exe" // ԭʼļ
+#define PRODUCT_NAME "Qt_Gif_Creator" // Ʒ
+#define ORGANIZATION_DOMAIN "https://github.com/xiaoyanLG" //
+
+// ͼ
+IDI_ICON1 ICON PRODUCT_ICON
+
+// 汾Ϣ
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION FILE_VERSION
+ PRODUCTVERSION PRODUCT_VERSION
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x40004L
+ FILETYPE 0x1L
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "080404b0"
+ BEGIN
+ VALUE "CompanyName", COMPANY_NAME
+ VALUE "FileDescription", FILE_DESCRIPTION
+ VALUE "FileVersion", FILE_VERSION_STR
+ VALUE "InternalName", INTERNAL_NAME
+ VALUE "LegalCopyright", LEGAL_COPYRIGHT
+ VALUE "OriginalFilename", ORIGINAL_FILE_NAME
+ VALUE "ProductName", PRODUCT_NAME
+ VALUE "ProductVersion", PRODUCT_VERSION_STR
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x804, 1200
+ END
+END
diff --git a/images.qrc b/images.qrc
new file mode 100644
index 0000000..d889c81
--- /dev/null
+++ b/images.qrc
@@ -0,0 +1,5 @@
+
+
+ gif.ico
+
+
diff --git a/main.cpp b/main.cpp
new file mode 100644
index 0000000..9ec0132
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,13 @@
+#include "xygifframe.h"
+#include
+#include
+
+int main(int argc, char *argv[])
+{
+ QApplication a(argc, argv);
+ a.setWindowIcon(QIcon(":/gif.ico"));
+ XYGifFrame w;
+ w.show();
+
+ return a.exec();
+}
diff --git a/xygifcreator.cpp b/xygifcreator.cpp
new file mode 100644
index 0000000..895e900
--- /dev/null
+++ b/xygifcreator.cpp
@@ -0,0 +1,103 @@
+#include "xygifcreator.h"
+#include
+#include
+
+#include "gif.h"
+
+class XYGif : public QObject
+{
+ Q_OBJECT
+public:
+ explicit XYGif()
+ {
+ moveToThread(&mWorkThread);
+ }
+ ~XYGif()
+ {
+ if (mWorkThread.isRunning())
+ {
+ mWorkThread.quit();
+ mWorkThread.wait();
+ }
+ }
+
+public slots:
+ void begin(const QString &file, int width, int height, int delay)
+ {
+ GifBegin(&mGifWriter, file.toUtf8().data(), static_cast(width), static_cast(height), static_cast(delay));
+ }
+ void frame(const QImage &img, int width, int height, int delay)
+ {
+ GifWriteFrame(&mGifWriter, img.bits(),
+ static_cast(qMin(width, img.width())),
+ static_cast(qMin(height, img.height())),
+ static_cast(100.0 / delay));
+ }
+ void end()
+ {
+ GifEnd(&mGifWriter);
+
+ mWorkThread.quit();
+ }
+
+private:
+ GifWriter mGifWriter;
+ QThread mWorkThread;
+
+ friend class XYGifCreator;
+};
+
+XYGifCreator::XYGifCreator(QObject *parent)
+ : QObject(parent)
+{
+ mGif = new XYGif;
+ connect(&mGif->mWorkThread, &QThread::finished, this, &XYGifCreator::finished);
+}
+
+XYGifCreator::~XYGifCreator()
+{
+ delete mGif;
+}
+
+void XYGifCreator::startThread()
+{
+ mGif->mWorkThread.start();
+}
+
+bool XYGifCreator::isRunning()
+{
+ return mGif->mWorkThread.isRunning();
+}
+
+void XYGifCreator::begin(const QString &file, int width, int height, int delay, Qt::ConnectionType type)
+{
+ mWidth = width;
+ mHeight = height;
+
+ QMetaObject::invokeMethod(mGif, "begin", type,
+ QGenericReturnArgument(),
+ Q_ARG(QString, file),
+ Q_ARG(int, width),
+ Q_ARG(int, height),
+ Q_ARG(int, delay));
+}
+
+void XYGifCreator::frame(const QImage &img, int delay, Qt::ConnectionType type)
+{
+ // gif.h 文件有描述目前只能是RGBA8888图片格式,并且alpha没有被使用
+ QImage img32 = img.convertToFormat(QImage::Format_RGBA8888);
+
+ QMetaObject::invokeMethod(mGif, "frame", type,
+ QGenericReturnArgument(),
+ Q_ARG(QImage, img32),
+ Q_ARG(int, mWidth),
+ Q_ARG(int, mHeight),
+ Q_ARG(int, delay));
+}
+
+void XYGifCreator::end(Qt::ConnectionType type)
+{
+ QMetaObject::invokeMethod(mGif, "end", type);
+}
+
+#include "xygifcreator.moc"
diff --git a/xygifcreator.h b/xygifcreator.h
new file mode 100644
index 0000000..39029ab
--- /dev/null
+++ b/xygifcreator.h
@@ -0,0 +1,32 @@
+#ifndef XYGIFCREATOR_H
+#define XYGIFCREATOR_H
+
+#include
+
+class XYGif;
+class XYGifCreator : public QObject
+{
+ Q_OBJECT
+public:
+ explicit XYGifCreator(QObject *parent = nullptr);
+ ~XYGifCreator();
+
+ void startThread();
+ bool isRunning();
+
+signals:
+ void finished();
+
+public slots:
+ void begin(const QString &file, int width, int height, int delay = 0, Qt::ConnectionType type = Qt::AutoConnection);
+ void frame(const QImage &img, int delay = 0, Qt::ConnectionType type = Qt::AutoConnection);
+ void end(Qt::ConnectionType type = Qt::AutoConnection);
+
+private:
+ XYGif *mGif;
+ int mWidth;
+ int mHeight;
+
+};
+
+#endif // XYGIFCREATOR_H
diff --git a/xygifframe.cpp b/xygifframe.cpp
new file mode 100644
index 0000000..b400a80
--- /dev/null
+++ b/xygifframe.cpp
@@ -0,0 +1,401 @@
+#include "xygifframe.h"
+#include "xypackimage.h"
+#include "ui_xygifframe.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef Q_OS_WIN32
+#include
+
+static QImage getScreenImage(int x, int y, int width, int height)
+{
+ HDC hScrDC = ::GetDC(nullptr);
+ HDC hMemDC = nullptr;
+
+ BYTE *lpBitmapBits = nullptr;
+
+ int nWidth = width;
+ int nHeight = height;
+
+ hMemDC = ::CreateCompatibleDC(hScrDC);
+
+ BITMAPINFO bi;
+ ZeroMemory(&bi, sizeof(BITMAPINFO));
+ bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+ bi.bmiHeader.biWidth = nWidth;
+ bi.bmiHeader.biHeight = nHeight;
+ bi.bmiHeader.biPlanes = 1;
+ bi.bmiHeader.biBitCount = 24;
+
+ CURSORINFO hCur ;
+ ZeroMemory(&hCur,sizeof(hCur));
+ hCur.cbSize = sizeof(CURSORINFO);
+ GetCursorInfo(&hCur);
+
+ ICONINFO IconInfo = {};
+ if(GetIconInfo(hCur.hCursor, &IconInfo))
+ {
+ hCur.ptScreenPos.x -= IconInfo.xHotspot;
+ hCur.ptScreenPos.y -= IconInfo.yHotspot;
+ if(nullptr != IconInfo.hbmMask)
+ DeleteObject(IconInfo.hbmMask);
+ if(nullptr != IconInfo.hbmColor)
+ DeleteObject(IconInfo.hbmColor);
+ }
+
+ HBITMAP bitmap = ::CreateDIBSection(hMemDC, &bi, DIB_RGB_COLORS, (LPVOID*)&lpBitmapBits, nullptr, 0);
+ HGDIOBJ oldbmp = ::SelectObject(hMemDC, bitmap);
+
+ ::BitBlt(hMemDC, 0, 0, nWidth, nHeight, hScrDC, x, y, SRCCOPY);
+ DrawIconEx(hMemDC, hCur.ptScreenPos.x - x, hCur.ptScreenPos.y - y, hCur.hCursor, 0, 0, 0, nullptr, DI_NORMAL | DI_COMPAT);
+
+ BITMAPFILEHEADER bh;
+ ZeroMemory(&bh, sizeof(BITMAPFILEHEADER));
+ bh.bfType = 0x4d42; //bitmap
+ bh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
+ bh.bfSize = bh.bfOffBits + ((nWidth*nHeight)*3);
+
+ int size = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 3 * nWidth * nHeight;
+ uchar *bmp = new uchar[size];
+ uint offset = 0;
+ memcpy(bmp, (char *)&bh, sizeof(BITMAPFILEHEADER));
+ offset = sizeof(BITMAPFILEHEADER);
+ memcpy(bmp + offset, (char *)&(bi.bmiHeader), sizeof(BITMAPINFOHEADER));
+ offset += sizeof(BITMAPINFOHEADER);
+ memcpy(bmp + offset, (char *)lpBitmapBits, 3 * nWidth * nHeight);
+
+ ::SelectObject(hMemDC, oldbmp);
+ ::DeleteObject(bitmap);
+ ::DeleteObject(hMemDC);
+ ::ReleaseDC(nullptr, hScrDC);
+ QImage image = QImage::fromData(bmp, size);
+ delete[] bmp;
+ return image;
+}
+#else // linux x11
+#include
+#include
+static void X11drawCursor(Display *display, QImage &image, int recording_area_x, int recording_area_y)
+{
+ // get the cursor
+ XFixesCursorImage *xcim = XFixesGetCursorImage(display);
+ if(xcim == NULL)
+ return;
+
+ // calculate the position of the cursor
+ int x = xcim->x - xcim->xhot - recording_area_x;
+ int y = xcim->y - xcim->yhot - recording_area_y;
+
+ // calculate the part of the cursor that's visible
+ int cursor_left = std::max(0, -x), cursor_right = std::min((int) xcim->width, image.width() - x);
+ int cursor_top = std::max(0, -y), cursor_bottom = std::min((int) xcim->height, image.height() - y);
+
+ unsigned int pixel_bytes, r_offset, g_offset, b_offset; // ARGB
+ pixel_bytes = 4;
+ r_offset = 2; g_offset = 1; b_offset = 0;
+
+ // draw the cursor
+ // XFixesCursorImage uses 'long' instead of 'int' to store the cursor images, which is a bit weird since
+ // 'long' is 64-bit on 64-bit systems and only 32 bits are actually used. The image uses premultiplied alpha.
+ for(int j = cursor_top; j < cursor_bottom; ++j) {
+ unsigned long *cursor_row = xcim->pixels + xcim->width * j;
+ uint8_t *image_row = (uint8_t*) image.bits() + image.bytesPerLine() * (y + j);
+ for(int i = cursor_left; i < cursor_right; ++i) {
+ unsigned long cursor_pixel = cursor_row[i];
+ uint8_t *image_pixel = image_row + pixel_bytes * (x + i);
+ int cursor_a = (uint8_t) (cursor_pixel >> 24);
+ int cursor_r = (uint8_t) (cursor_pixel >> 16);
+ int cursor_g = (uint8_t) (cursor_pixel >> 8);
+ int cursor_b = (uint8_t) (cursor_pixel >> 0);
+ if(cursor_a == 255) {
+ image_pixel[r_offset] = cursor_r;
+ image_pixel[g_offset] = cursor_g;
+ image_pixel[b_offset] = cursor_b;
+ } else {
+ image_pixel[r_offset] = (image_pixel[r_offset] * (255 - cursor_a) + 127) / 255 + cursor_r;
+ image_pixel[g_offset] = (image_pixel[g_offset] * (255 - cursor_a) + 127) / 255 + cursor_g;
+ image_pixel[b_offset] = (image_pixel[b_offset] * (255 - cursor_a) + 127) / 255 + cursor_b;
+ }
+ }
+ }
+ // free the cursor
+ XFree(xcim);
+}
+#endif
+
+XYGifFrame::XYGifFrame(QWidget *parent)
+ : XYMovableWidget(parent),
+ mStartResize(false),
+ ui(new Ui::XYGifFrame)
+{
+ ui->setupUi(this);
+ mGifCreator = new XYGifCreator(this);
+ mTimer.setSingleShot(false);
+ setWindowFlags( Qt::WindowStaysOnTopHint);
+
+ ui->width->installEventFilter(this);
+ ui->height->installEventFilter(this);
+ connect(ui->width, SIGNAL(editingFinished()), this, SLOT(doResize()));
+ connect(ui->height, SIGNAL(editingFinished()), this, SLOT(doResize()));
+ connect(ui->gif, SIGNAL(clicked()), this, SLOT(active()));
+ connect(ui->img, SIGNAL(clicked()), this, SLOT(packImages()));
+ connect(&mTimer, SIGNAL(timeout()), this, SLOT(frame()));
+ connect(ui->save, SIGNAL(clicked()), this, SLOT(setGifFile()));
+ connect(mGifCreator, &XYGifCreator::finished, this, [this](){
+ this->ui->tips->setText(QStringLiteral("保存Gif完成!"));
+ });
+
+ ui->content->adjustSize();
+ setMouseTracking(true);
+ setMinimumSize(162, 150);
+ ui->gif->setFocus();
+ m_tray = new QSystemTrayIcon(this);//实例化
+ QPixmap m_logo(":/gif.ico");
+ m_tray->setIcon(QIcon(m_logo));//设置图标
+ m_tray->show();
+ connect(m_tray,&QSystemTrayIcon::activated,this,&XYGifFrame::TrayIconAction);
+ m_menu = new QMenu(this);
+ m_resetAction = new QAction(this);
+ m_resetAction->setText("show");
+ m_quitAction = new QAction(this);
+ m_resetAction->setIcon(QIcon(m_logo));
+ m_quitAction->setText("quit");
+ m_quitAction->setIcon(QIcon(m_logo));
+ connect(m_quitAction,&QAction::triggered,qApp,&QApplication::quit);
+ connect(m_resetAction,&QAction::triggered,this,&XYGifFrame::restory);
+// connect(m_closeDialog,&closeDialog::traySiganal,this,&XYGifFrame::Tray);
+ m_tray->setContextMenu(m_menu);//设置托盘菜单
+ m_menu->addAction(m_resetAction);
+ m_menu->addAction(m_quitAction);
+
+}
+
+XYGifFrame::~XYGifFrame()
+{
+ delete ui;
+}
+
+void XYGifFrame::doResize()
+{
+ QRect rect(pos(), QSize(ui->width->value(), ui->height->value()));
+ rect.adjust(-3, -3, 3, ui->content->height() + 5);
+
+ resize(rect.size());
+}
+
+void XYGifFrame::packImages()
+{
+ XYPackImage img(this);
+ img.exec();
+}
+
+void XYGifFrame::setGifFile()
+{
+ mGifFile = QFileDialog::getSaveFileName(this, "", QString("xy-%1.gif").arg(QDateTime::currentDateTime().toString("yyyyMMdd-hhmmss")));
+
+ ui->save->setToolTip(mGifFile);
+}
+
+void XYGifFrame::active()
+{
+ if (!mTimer.isActive())
+ {
+ if (mGifCreator->isRunning())
+ {
+ QMessageBox::warning(this, QStringLiteral("提醒"), QStringLiteral("正在保存Gif,请稍等!"));
+ return;
+ }
+ start();
+ }
+ else
+ {
+ stop();
+ }
+}
+
+void XYGifFrame::start()
+{
+ if (!mGifFile.isEmpty())
+ {
+ mGifCreator->startThread();
+
+ mGifCreator->begin(mGifFile.toUtf8().data(), ui->width->value(), ui->height->value(), 1);
+
+ mPixs = 0;
+ ui->gif->setText(QStringLiteral("停止录制"));
+
+ frame();
+ mTimer.start(static_cast(1000.0 / ui->interval->value()));
+ ui->width->setDisabled(true);
+ ui->height->setDisabled(true);
+ ui->interval->setDisabled(true);
+ }
+ else
+ {
+ QMessageBox::warning(this, QStringLiteral("提醒"), QStringLiteral("请先设置保存gif的位置!"));
+ }
+}
+
+void XYGifFrame::stop()
+{
+ mTimer.stop();
+ ui->gif->setText(QStringLiteral("开始录制"));
+ mGifCreator->end();
+
+ ui->width->setEnabled(true);
+ ui->height->setEnabled(true);
+ ui->interval->setEnabled(true);
+ this->ui->tips->setText(QStringLiteral("请等待保存Gif..."));
+}
+
+void XYGifFrame::frame()
+{
+ QImage img = getCurScreenImage();
+ if (!img.isNull())
+ {
+ mGifCreator->frame(img, ui->interval->value());
+ mPixs++;
+
+ ui->tips->setText(QStringLiteral("已保存 %1 张图片").arg(mPixs));
+ }
+}
+
+bool XYGifFrame::eventFilter(QObject *watched, QEvent *event)
+{
+ if (watched == ui->width
+ || watched == ui->height)
+ {
+ if (event->type() == QEvent::Wheel)
+ {
+ doResize();
+ }
+ }
+ return XYMovableWidget::eventFilter(watched, event);
+}
+
+void XYGifFrame::paintEvent(QPaintEvent *)
+{
+ QPainter painter(this);
+ painter.fillRect(rect(), Qt::gray);
+}
+
+void XYGifFrame::resizeEvent(QResizeEvent *)
+{
+ QRect rect = this->rect();
+ rect.adjust(3, 3, -3, -(ui->content->height() + 5));
+ mRecordRect = rect;
+
+ ui->width->setValue(mRecordRect.width());
+ ui->height->setValue(mRecordRect.height());
+
+ QRegion region(this->rect());
+ setMask(region.xored(mRecordRect));
+ qDebug()<content->move(width() - ui->content->width() - 3, height() - ui->content->height() - 3);
+}
+
+void XYGifFrame::mousePressEvent(QMouseEvent *event)
+{
+ QRect rect(QPoint(width() - 3, height() - 3), QSize(3, 3));
+ if (rect.contains(event->pos()) && !mTimer.isActive())
+ {
+ mStartResize = true;
+ mStartGeometry = QRect(event->globalPos(), size());
+ }
+ else
+ {
+ XYMovableWidget::mousePressEvent(event);
+ }
+}
+
+void XYGifFrame::mouseReleaseEvent(QMouseEvent *event)
+{
+ mStartResize = false;
+ setCursor(Qt::ArrowCursor);
+ XYMovableWidget::mouseReleaseEvent(event);
+}
+
+void XYGifFrame::mouseMoveEvent(QMouseEvent *event)
+{
+ QRect rect(QPoint(width() - 3, height() - 3), QSize(3, 3));
+
+ if (mStartResize)
+ {
+ QPoint ch = event->globalPos() - mStartGeometry.topLeft();
+ resize(mStartGeometry.size() + QSize(ch.x(), ch.y()));
+ }
+ else if (rect.contains(event->pos()) && !mTimer.isActive())
+ {
+ setCursor(Qt::SizeFDiagCursor);
+ }
+ else
+ {
+ setCursor(Qt::ArrowCursor);
+ XYMovableWidget::mouseMoveEvent(event);
+ }
+}
+
+void XYGifFrame::wheelEvent(QWheelEvent *event)
+{
+ if (event->delta() < 0)
+ {
+ ui->content->move(ui->content->x() + 6, ui->content->y());
+ }
+ else
+ {
+ ui->content->move(ui->content->x() - 6, ui->content->y());
+ }
+
+ XYMovableWidget::wheelEvent(event);
+}
+
+QImage XYGifFrame::getCurScreenImage()
+{
+ QImage img;
+#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
+ auto screen = qApp->screenAt(pos());
+#else
+ QScreen *screen = nullptr;
+ foreach (screen, qApp->screens())
+ {
+ if (screen->geometry().contains(pos()))
+ {
+ break;
+ }
+ }
+#endif
+ if (screen != nullptr)
+ {
+#ifdef Q_OS_WIN32
+ img = getScreenImage(x() + mRecordRect.x(),
+ y() + mRecordRect.y(),
+ mRecordRect.width(),
+ mRecordRect.height());
+#else
+ img = screen->grabWindow(0,
+ x() + mRecordRect.x(),
+ y() + mRecordRect.y(),
+ mRecordRect.width(),
+ mRecordRect.height()).toImage();
+
+ // 需要系统是x11后端
+ auto display = XOpenDisplay(NULL);
+ X11drawCursor(display, img, x() + mRecordRect.x(), y() + mRecordRect.y());
+ XCloseDisplay(display);
+#endif
+ }
+
+ return img;
+}
+
+void XYGifFrame::on_quit_clicked()
+{
+ this->hide();
+}
diff --git a/xygifframe.h b/xygifframe.h
new file mode 100644
index 0000000..4d717d2
--- /dev/null
+++ b/xygifframe.h
@@ -0,0 +1,76 @@
+#ifndef XYGIFFRAME_H
+#define XYGIFFRAME_H
+
+#include "xymovablewidget.h"
+#include "xygifcreator.h"
+#include
+#include
+
+namespace Ui {
+class XYGifFrame;
+}
+
+class XYGifFrame : public XYMovableWidget
+{
+ Q_OBJECT
+public:
+ explicit XYGifFrame(QWidget *parent = nullptr);
+ ~XYGifFrame() override;
+
+public slots:
+ void doResize();
+ void packImages();
+ void setGifFile();
+ void active();
+ void start();
+ void stop();
+ void frame();
+
+ void TrayIconAction(QSystemTrayIcon::ActivationReason reason)
+ {
+
+ if(reason == QSystemTrayIcon::Trigger)
+ this->showNormal();
+ }
+ void restory(){
+ this->showNormal();
+ }
+protected:
+ bool eventFilter(QObject *watched, QEvent *event) override;
+ void paintEvent(QPaintEvent *event) override;
+ void resizeEvent(QResizeEvent *event) override;
+ void mousePressEvent(QMouseEvent *event) override;
+ void mouseReleaseEvent(QMouseEvent *event) override;
+ void mouseMoveEvent(QMouseEvent *event) override;
+ void wheelEvent(QWheelEvent *event) override;
+
+private slots:
+ void on_quit_clicked();
+
+private:
+ QImage getCurScreenImage();
+
+ // 用于resize窗口
+private:
+ bool mStartResize;
+ QRect mStartGeometry;
+
+private:
+ Ui::XYGifFrame *ui;
+ QRect mRecordRect;
+ XYGifCreator *mGifCreator;
+ QTimer mTimer;
+ int mPixs;
+ QString mGifFile;
+
+ QSystemTrayIcon* m_tray; //
+
+ QMenu* m_menu; //̲˵
+
+ QAction* m_resetAction; //̰ť
+
+ QAction* m_quitAction; //̰ť
+
+};
+
+#endif // XYGIFFRAME_H
diff --git a/xygifframe.ui b/xygifframe.ui
new file mode 100644
index 0000000..a92c82f
--- /dev/null
+++ b/xygifframe.ui
@@ -0,0 +1,200 @@
+
+
+ XYGifFrame
+
+
+
+ 0
+ 0
+ 757
+ 418
+
+
+
+ Form
+
+
+
+
+ 40
+ 210
+ 673
+ 41
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 48
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 47
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ 宽:
+
+
+
+ -
+
+
+ 9999
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ 高:
+
+
+
+ -
+
+
+ 9999
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ 帧率:
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 50
+ 0
+
+
+
+
+
+
+
+
+
+ 1
+
+
+ 100
+
+
+ 8
+
+
+
+ -
+
+
+ 打包图片为GIF
+
+
+
+ -
+
+
+ 设置保存位置
+
+
+
+ -
+
+
+ 开始录制
+
+
+
+ -
+
+
+ 退出
+
+
+
+
+
+
+
+
+
diff --git a/xymovablewidget.cpp b/xymovablewidget.cpp
new file mode 100644
index 0000000..f557b25
--- /dev/null
+++ b/xymovablewidget.cpp
@@ -0,0 +1,44 @@
+#include "xymovablewidget.h"
+#include
+
+XYMovableWidget::XYMovableWidget(QWidget *parent)
+ : QWidget(parent)
+{
+ mbLeftMousePressed = false;
+}
+
+XYMovableWidget::~XYMovableWidget()
+{
+
+}
+
+void XYMovableWidget::mousePressEvent(QMouseEvent *event)
+{
+ if (event->button() == Qt::LeftButton)
+ {
+ mbLeftMousePressed = true;
+ moLastPos = event->globalPos();
+ }
+}
+
+void XYMovableWidget::mouseReleaseEvent(QMouseEvent *event)
+{
+ if (event->button() == Qt::LeftButton)
+ {
+ mbLeftMousePressed = false;
+ moLastPos = event->globalPos();
+ }
+}
+
+void XYMovableWidget::mouseMoveEvent(QMouseEvent *event)
+{
+ if (mbLeftMousePressed)
+ {
+ QPoint lastpos = pos();
+ lastpos.setX( lastpos.x() + event->globalX() - moLastPos.x());
+ lastpos.setY( lastpos.y() + event->globalY() - moLastPos.y());
+ move(lastpos);
+ moLastPos = event->globalPos();
+ }
+}
+
diff --git a/xymovablewidget.h b/xymovablewidget.h
new file mode 100644
index 0000000..47ce2f3
--- /dev/null
+++ b/xymovablewidget.h
@@ -0,0 +1,24 @@
+#ifndef XYMOVABLEWIDGET_H
+#define XYMOVABLEWIDGET_H
+
+#include
+
+class XYMovableWidget : public QWidget
+{
+ Q_OBJECT
+public:
+ explicit XYMovableWidget(QWidget *parent = 0);
+ ~XYMovableWidget();
+
+protected:
+ void mousePressEvent(QMouseEvent *event);
+ void mouseReleaseEvent(QMouseEvent *event);
+ void mouseMoveEvent(QMouseEvent *event);
+
+protected:
+ bool mbLeftMousePressed;
+ QPoint moLastPos;
+
+};
+
+#endif // XYMOVABLEWIDGET_H
diff --git a/xypackimage.cpp b/xypackimage.cpp
new file mode 100644
index 0000000..b242d24
--- /dev/null
+++ b/xypackimage.cpp
@@ -0,0 +1,110 @@
+#include "xypackimage.h"
+#include "xygifcreator.h"
+#include "ui_xypackimage.h"
+#include
+#include
+#include
+#include
+#include
+#include
+
+XYPackImage::XYPackImage(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::XYPackImage)
+{
+ setWindowFlags(windowFlags() ^ Qt::WindowContextHelpButtonHint);
+ ui->setupUi(this);
+
+ mWorkThread = new Work;
+ connect(this, &XYPackImage::pickImags, mWorkThread, &Work::pickImags);
+
+ connect(mWorkThread, &Work::progress, ui->progressBar, &QProgressBar::setValue);
+}
+
+XYPackImage::~XYPackImage()
+{
+ delete mWorkThread;
+ delete ui;
+}
+
+void XYPackImage::on_pushButton_2_clicked() // 添加图片
+{
+ QStringList files = QFileDialog::getOpenFileNames(this, "", "", "Images (*.png *.jpg)");
+
+ for (int i = 0; i < files.size(); ++i)
+ {
+ QListWidgetItem *item = new QListWidgetItem;
+ item->setIcon(QIcon(files.at(i)));
+ item->setText(files.at(i));
+ ui->listWidget->addItem(item);
+ }
+}
+
+void XYPackImage::on_pushButton_clicked() // 开始打包
+{
+ QStringList images;
+ for (int row = 0; row < ui->listWidget->count(); ++row)
+ {
+ images << ui->listWidget->item(row)->text();
+ }
+
+ if (images.isEmpty())
+ {
+ QMessageBox::warning(this, QStringLiteral("提醒"), QStringLiteral("请先选择图片!"));
+ return;
+ }
+
+ if (!mGifFile.isEmpty())
+ {
+ int w = ui->width->value();
+ int h = ui->height->value();
+ int delay = ui->delay->value();
+
+ emit pickImags(mGifFile, images, w, h, delay);
+ }
+ else
+ {
+ QMessageBox::warning(this, QStringLiteral("提醒"), QStringLiteral("请先设置保存gif的位置!"));
+ }
+}
+
+Work::Work()
+{
+ mThread = new QThread;
+ mThread->start();
+ moveToThread(mThread);
+}
+
+Work::~Work()
+{
+ if (mThread->isRunning()) {
+ mThread->quit();
+ mThread->wait();
+ }
+
+ delete mThread;
+}
+
+void Work::pickImags(const QString &file, const QStringList &imgs, int w, int h, int delay)
+{
+ XYGifCreator gif;
+ gif.begin(file.toUtf8().data(), w, h, 1, Qt::DirectConnection);
+ emit progress(0);
+ for (int i = 0; i < imgs.size(); ++i)
+ {
+ QImage img(imgs.at(i));
+ img = img.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
+ gif.frame(img, delay, Qt::DirectConnection);
+
+ progress(static_cast((i + 1.0) / imgs.size() * 100));
+ }
+ gif.end(Qt::DirectConnection);
+
+ emit progress(100);
+}
+
+void XYPackImage::on_pushButton_3_clicked()
+{
+ mGifFile = QFileDialog::getSaveFileName(this, "",
+ QString("xy-%1.gif").arg(QDateTime::currentDateTime().toString("yyyyMMdd-hhmmss")));
+}
diff --git a/xypackimage.h b/xypackimage.h
new file mode 100644
index 0000000..51e1b44
--- /dev/null
+++ b/xypackimage.h
@@ -0,0 +1,52 @@
+#ifndef XYPACKIMAGE_H
+#define XYPACKIMAGE_H
+
+#include
+
+namespace Ui {
+class XYPackImage;
+}
+
+class QThread;
+class Work: public QObject
+{
+ Q_OBJECT
+public:
+ Work();
+ ~Work();
+
+signals:
+ void progress(int progress);
+
+public slots:
+ void pickImags(const QString &file, const QStringList &imgs, int w, int h, int delay);
+
+private:
+ QThread *mThread;
+};
+
+class XYPackImage : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit XYPackImage(QWidget *parent = nullptr);
+ ~XYPackImage();
+
+signals:
+ void pickImags(const QString &file, const QStringList &imgs, int w, int h, int delay);
+
+private slots:
+ void on_pushButton_2_clicked();
+
+ void on_pushButton_clicked();
+
+ void on_pushButton_3_clicked();
+
+private:
+ Ui::XYPackImage *ui;
+ Work *mWorkThread;
+ QString mGifFile;
+};
+
+#endif // XYPACKIMAGE_H
diff --git a/xypackimage.ui b/xypackimage.ui
new file mode 100644
index 0000000..01b9b83
--- /dev/null
+++ b/xypackimage.ui
@@ -0,0 +1,206 @@
+
+
+ XYPackImage
+
+
+
+ 0
+ 0
+ 685
+ 402
+
+
+
+ 打包GIF
+
+
+ -
+
+
-
+
+
+ false
+
+
+ false
+
+
+ true
+
+
+ false
+
+
+ QAbstractItemView::DragDrop
+
+
+ Qt::MoveAction
+
+
+ false
+
+
+
+ 200
+ 200
+
+
+
+ QListView::ListMode
+
+
+ 100
+
+
+
+ -
+
+
-
+
+
+ 宽:
+
+
+
+ -
+
+
+ 1
+
+
+ 9999
+
+
+ 400
+
+
+
+ -
+
+
+ 高:
+
+
+
+ -
+
+
+ 1
+
+
+ 9999
+
+
+ 400
+
+
+
+ -
+
+
+ 帧率:
+
+
+
+ -
+
+
+
+ 50
+ 0
+
+
+
+
+
+
+ 1
+
+
+ 100
+
+
+ 10
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ <html><head/><body><p align="center"><span style=" font-size:14pt; font-weight:600; color:#ab5c4c;">可以拖拽</span></p><p align="center"><span style=" font-size:14pt; font-weight:600; color:#ab5c4c;">图片来改</span></p><p align="center"><span style=" font-size:14pt; font-weight:600; color:#ab5c4c;">变顺序</span></p></body></html>
+
+
+ Qt::RichText
+
+
+ 0
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ 添加图片
+
+
+
+ -
+
+
+ 设置保存位置
+
+
+
+ -
+
+
+ 打包
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+ 0
+
+
+
+
+
+
+
+
+
+