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 + +![](ShowPictures/1.gif) 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 + + + + + + + + + +