From: John Tsiombikas Date: Sat, 12 Dec 2020 07:15:12 +0000 (+0200) Subject: - added libdrawtext X-Git-Url: http://git.mutantstargoat.com/user/nuclear/?p=demo_prior;a=commitdiff_plain;h=2f14a35e7d557da12f24056267b911f24774aa18 - added libdrawtext - added an OSD system for messages - added fps counter - added imtk gui code (TODO: finish text drawing) --- diff --git a/Makefile b/Makefile index 7dcbb03..0ccf3ae 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -src = $(wildcard src/*.c) +src = $(wildcard src/*.c) $(wildcard src/imtk/*.c) obj = $(src:.c=.o) dep = $(src:.c=.d) bin = demo @@ -6,12 +6,13 @@ bin = demo warn = -pedantic -Wall -g def = -DMINIGLUT_USE_LIBC -incpath = -Ilibs/cgmath -Ilibs/glew -Ilibs/treestore/src -Ilibs/imago2/src -libpath = -Llibs/glew -Llibs/treestore -Llibs/imago2 +incpath = -Isrc -Ilibs/cgmath -Ilibs/glew -Ilibs/treestore/src \ + -Ilibs/imago2/src -Ilibs/drawtext/src +libpath = -Llibs/glew -Llibs/treestore -Llibs/imago2 -Llibs/drawtext CFLAGS = $(warn) $(def) $(incpath) -MMD -LDFLAGS = $(libpath) -lX11 -lXext -lGL -lGLU -lglut -lglew_static -ltreestore \ - -limago -lpng -lz -ljpeg -lm +LDFLAGS = $(libpath) -ltreestore -limago -lpng -lz -ljpeg -ldrawtext \ + -lglew_static -lGL -lGLU -lglut -lX11 -lXext -lm $(bin): $(obj) libs $(CC) -o $@ $(obj) $(LDFLAGS) diff --git a/libs/Makefile b/libs/Makefile index b1e2d2f..f35b0c5 100644 --- a/libs/Makefile +++ b/libs/Makefile @@ -1,8 +1,8 @@ .PHONY: all -all: glew treestore imago +all: glew treestore imago drawtext .PHONY: clean -clean: glew-clean treestore-clean imago-clean +clean: glew-clean treestore-clean imago-clean drawtext-clean .PHONY: glew glew: @@ -27,3 +27,11 @@ imago: .PHONY: imago-clean imago-clean: $(MAKE) -C imago2 clean + +.PHONY: drawtext +drawtext: + $(MAKE) -C drawtext + +.PHONY: drawtext-clean +drawtext-clean: + $(MAKE) -C drawtext clean diff --git a/libs/drawtext/COPYING b/libs/drawtext/COPYING new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/libs/drawtext/COPYING @@ -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/libs/drawtext/COPYING.LESSER b/libs/drawtext/COPYING.LESSER new file mode 100644 index 0000000..65c5ca8 --- /dev/null +++ b/libs/drawtext/COPYING.LESSER @@ -0,0 +1,165 @@ + GNU LESSER 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. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser 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 +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/libs/drawtext/Makefile b/libs/drawtext/Makefile new file mode 100644 index 0000000..c29607c --- /dev/null +++ b/libs/drawtext/Makefile @@ -0,0 +1,12 @@ +src = $(wildcard src/*.c) +obj = $(src:.c=.o) +lib = libdrawtext.a + +CFLAGS = -pedantic -Wall -g -O3 -DNO_FREETYPE + +$(lib): $(obj) + $(AR) rcs $@ $(obj) + +.PHONY: clean +clean: + rm -f $(obj) $(lib) diff --git a/libs/drawtext/README.md b/libs/drawtext/README.md new file mode 100644 index 0000000..b4ac23a --- /dev/null +++ b/libs/drawtext/README.md @@ -0,0 +1,59 @@ +libdrawtext +=========== + +About +----- +Libdrawtext is a simple library for fast anti-aliased text rendering in OpenGL. + +Since version 0.3 libdrawtext can also render text on plain RGBA pixel buffers. + +Libdrawtext uses freetype2 for glyph rasterization. If you would rather avoid +having freetype2 as a dependency, you can optionally compile libdrawtext +without it, and use pre-rendered glyphmaps. Glyphmaps can be generated by the +included font2glyphmap tool, or by calling `dtx_save_glyphmap`. + +See examples subdir for simple programs demonstrating libdrawtext usage, and +refer to the heavily commented drawtext.h header file. + +- website: http://nuclear.mutantstargoat.com/sw/libdrawtext +- repository (git): https://github.com/jtsiomb/libdrawtext.git + +Dependencies +------------ +- OpenGL (optional) +- freetype2 (optional): http://www.freetype.org + +License +------- +Copyright (C) 2011-2019 John Tsiombikas +You may freely use, modify and/or redistribute libdrawtext, under the terms of +the GNU Lesser General Public License (LGPL) version 3 (or at your option, any +later version published by the Free Software Foundation). See COPYING, and +COPYING.LESSER for details. + +Build +----- +To build and install `libdrawtext` on UNIX or on Windows with MinGW, run: + + ./configure + make + make install + +See `./configure --help` for build-time options. + +To cross-compile for windows with mingw-w64, try the following incantation: + + ./configure --prefix=/usr/i686-w64-mingw32 + make CC=i686-w64-mingw32-gcc AR=i686-w64-mingw32-ar sys=mingw + make install sys=mingw + +Previous versions of this library included a visual studio project file. As I'm +not able to maintain it, I decided to remove it completely from this release. +The only way it can return in future releases, is if someone steps up to +maintain it. Send me an e-mail if you're interested. + +Contact +------- +Feel free to send in bug reports, patches, and comments to: nuclear@member.fsf.org + +Only plain text email messages, hard-wrapped at 72 columns will be accepted. diff --git a/libs/drawtext/src/draw.c b/libs/drawtext/src/draw.c new file mode 100644 index 0000000..a35b4d4 --- /dev/null +++ b/libs/drawtext/src/draw.c @@ -0,0 +1,108 @@ +/* +libdrawtext - a simple library for fast text rendering in OpenGL +Copyright (C) 2011-2016 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see . +*/ +#include +#include +#include +#if defined(WIN32) || defined(__WIN32__) +#include +#else +#include +#endif +#include "drawtext.h" +#include "drawtext_impl.h" + +void dtx_position(float x, float y) +{ + dtx_cur_offset[0] = x; + dtx_cur_offset[1] = y; +} + +void dtx_color(float r, float g, float b, float a) +{ + dtx_cur_color[0] = r; + dtx_cur_color[1] = g; + dtx_cur_color[2] = b; + dtx_cur_color[3] = a; + + dtx_cur_color_int[0] = r > 1.0 ? 255 : (int)(r * 255.0); + dtx_cur_color_int[1] = g > 1.0 ? 255 : (int)(g * 255.0); + dtx_cur_color_int[2] = b > 1.0 ? 255 : (int)(b * 255.0); + dtx_cur_color_int[3] = a > 1.0 ? 255 : (int)(a * 255.0); +} + +void dtx_string(const char *str) +{ + dtx_substring(str, 0, strlen(str)); +} + +void dtx_substring(const char *str, int start, int end) +{ + int should_flush = dtx_buf_mode == DTX_NBF; + float pos_x = dtx_cur_offset[0]; + float pos_y = dtx_cur_offset[1]; + + if(!dtx_font) { + return; + } + + /* skip start characters */ + while(*str && start > 0) { + str = dtx_utf8_next_char((char*)str); + --start; + --end; + } + + while(*str && --end >= 0) { + str = dtx_drawchar(str, &pos_x, &pos_y, &should_flush); + } + + if(should_flush) { + dtx_drawflush(); + } +} + +void dtx_printf(const char *fmt, ...) +{ + va_list ap; + int buf_size; + char *buf, tmp; + + if(!dtx_font) { + return; + } + + va_start(ap, fmt); + buf_size = vsnprintf(&tmp, 0, fmt, ap); + va_end(ap); + + if(buf_size == -1) { + buf_size = 512; + } + + buf = alloca(buf_size + 1); + va_start(ap, fmt); + vsnprintf(buf, buf_size + 1, fmt, ap); + va_end(ap); + + dtx_string(buf); +} + +void dtx_flush(void) +{ + dtx_drawflush(); +} diff --git a/libs/drawtext/src/drawgl.c b/libs/drawtext/src/drawgl.c new file mode 100644 index 0000000..4bf161e --- /dev/null +++ b/libs/drawtext/src/drawgl.c @@ -0,0 +1,441 @@ +/* +libdrawtext - a simple library for fast text rendering in OpenGL +Copyright (C) 2011-2018 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see . +*/ +#include "drawtext.h" +#include "drawtext_impl.h" + +struct quad { + struct dtx_vertex v[6]; +}; + +struct dtx_glyphmap *cur_gmap; + +#define QBUF_SZ 512 +static struct quad *qbuf; +static int num_quads; + +static dtx_user_draw_func user_draw_func; +static void *user_cls; + +static int dtx_draw_init(void); +static void cleanup(void); + +static void set_glyphmap_texture(struct dtx_glyphmap *gmap); +static const char *drawchar(const char *str, float *pos_x, float *pos_y, int *should_flush); +static void flush_user(void); +static void add_glyph(struct glyph *g, float x, float y); + + +#ifndef NO_OPENGL +#include +#include +#include +#include + +#ifdef TARGET_IPHONE +#include +#ifndef GL_ES +#define GL_ES +#endif + +#elif defined(ANDROID) +#include +#ifndef GL_ES +#define GL_ES +#endif + +#else /* regular OpenGL */ + +#ifdef WIN32 +#include +#endif + +#ifdef __APPLE__ +#include + +#else + +#define GL_GLEXT_LEGACY /* don't include glext.h internally in gl.h */ +#include +#ifndef NO_GLU +#include +#endif + +#ifdef __unix__ +#define GLX_GLXEXT_LEGACY /* don't include glxext.h internally in glx.h */ +#include +#endif + +#endif /* !__APPLE__ */ +#endif /* !TARGET_IPHONE */ + +#ifdef GL_ES +#define GL_CLAMP GL_CLAMP_TO_EDGE +#endif + +static void dtx_gl_init(void); +static void cleanup(void); +static void flush(void); + +static int vattr = -1; +static int tattr = -1; +static int cattr = -1; +static unsigned int font_tex; + +#ifndef GL_ES +#ifndef GL_VERSION_1_5 +#define GL_ARRAY_BUFFER 0x8892 +#define GL_ARRAY_BUFFER_BINDING 0x8894 +typedef void (APIENTRY *PFNGLBINDBUFFERPROC)(GLenum target, GLuint buffer); +static PFNGLBINDBUFFERPROC glBindBuffer; +#endif + +#ifndef GL_VERSION_2_0 +typedef void (APIENTRY *PFNGLENABLEVERTEXATTRIBARRAYPROC)(GLuint index); +typedef void (APIENTRY *PFNGLDISABLEVERTEXATTRIBARRAYPROC)(GLuint index); +typedef void (APIENTRY *PFNGLVERTEXATTRIBPOINTERPROC)(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +static PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray; +static PFNGLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray; +static PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer; +#endif + +#ifdef WIN32 +#define load_glfunc(s) wglGetProcAddress(s) +#elif defined(__unix__) +#define load_glfunc(s) glXGetProcAddress((unsigned char*)s) +#endif + +#endif /* !GL_ES */ + +void dtx_target_opengl(void) +{ + dtx_draw_init(); + dtx_drawchar = drawchar; + dtx_drawflush = flush; + + user_draw_func = 0; +} + +int dtx_gl_setopt(enum dtx_option opt, int val) +{ + switch(opt) { + case DTX_GL_ATTR_VERTEX: + vattr = val; + break; + case DTX_GL_ATTR_TEXCOORD: + tattr = val; + break; + case DTX_GL_ATTR_COLOR: + cattr = val; + break; + default: + return -1; + } + return 0; +} + +int dtx_gl_getopt(enum dtx_option opt, int *res) +{ + switch(opt) { + case DTX_GL_ATTR_VERTEX: + *res = vattr; + break; + case DTX_GL_ATTR_TEXCOORD: + *res = tattr; + break; + case DTX_GL_ATTR_COLOR: + *res = cattr; + break; + default: + return -1; + } + return 0; +} + +static void dtx_gl_init(void) +{ +#ifndef GL_ES +#ifndef GL_VERSION_1_5 + glBindBuffer = (PFNGLBINDBUFFERPROC)load_glfunc("glBindBuffer"); +#endif +#ifndef GL_VERSION_2_0 + glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC)load_glfunc("glEnableVertexAttribArray"); + glDisableVertexAttribArray = (PFNGLDISABLEVERTEXATTRIBARRAYPROC)load_glfunc("glDisableVertexAttribArray"); + glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)load_glfunc("glVertexAttribPointer"); +#endif +#endif /* !GL_ES */ +} + + +void dtx_vertex_attribs(int vert_attr, int tex_attr) +{ + vattr = vert_attr; + tattr = tex_attr; +} + +static void set_glyphmap_texture_gl(struct dtx_glyphmap *gmap) +{ + if(!gmap->tex) { + glGenTextures(1, &gmap->tex); + glBindTexture(GL_TEXTURE_2D, gmap->tex); +#if !defined(GL_ES) && defined(NO_GLU) + /* TODO: ideally we want to have mipmaps even without GLU, and we should + * just use SGIS_generate_mipmaps if available + */ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +#else + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); +#endif + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + gmap->tex_valid = 0; + } + + if(!gmap->tex_valid) { + glBindTexture(GL_TEXTURE_2D, gmap->tex); +#ifdef GL_ES + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, gmap->xsz, gmap->ysz, 0, GL_ALPHA, GL_UNSIGNED_BYTE, gmap->pixels); + glGenerateMipmap(GL_TEXTURE_2D); +#elif !defined(NO_GLU) + gluBuild2DMipmaps(GL_TEXTURE_2D, GL_ALPHA, gmap->xsz, gmap->ysz, GL_ALPHA, GL_UNSIGNED_BYTE, gmap->pixels); +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, gmap->xsz, gmap->ysz, 0, GL_ALPHA, GL_UNSIGNED_BYTE, gmap->pixels); +#endif + gmap->tex_valid = 1; + } + + font_tex = gmap->tex; +} + + + +static void flush(void) +{ + int vbo; + + if(!num_quads) { + return; + } + + if(glBindBuffer) { + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, 0); + } + +#ifndef GL_ES + glPushAttrib(GL_ENABLE_BIT); + glEnable(GL_TEXTURE_2D); +#endif + glBindTexture(GL_TEXTURE_2D, font_tex); + + if(vattr != -1 && glEnableVertexAttribArray) { + glEnableVertexAttribArray(vattr); + glVertexAttribPointer(vattr, 2, GL_FLOAT, 0, sizeof(struct dtx_vertex), qbuf); +#ifndef GL_ES + } else { + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(2, GL_FLOAT, sizeof(struct dtx_vertex), qbuf); +#endif + } + if(tattr != -1 && glEnableVertexAttribArray) { + glEnableVertexAttribArray(tattr); + glVertexAttribPointer(tattr, 2, GL_FLOAT, 0, sizeof(struct dtx_vertex), &qbuf->v[0].s); +#ifndef GL_ES + } else { + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer(2, GL_FLOAT, sizeof(struct dtx_vertex), &qbuf->v[0].s); +#endif + } + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glDepthMask(0); + + glDrawArrays(GL_TRIANGLES, 0, num_quads * 6); + + glDepthMask(1); + + if(vattr != -1 && glDisableVertexAttribArray) { + glDisableVertexAttribArray(vattr); +#ifndef GL_ES + } else { + glDisableClientState(GL_VERTEX_ARRAY); +#endif + } + if(tattr != -1 && glDisableVertexAttribArray) { + glDisableVertexAttribArray(tattr); +#ifndef GL_ES + } else { + glDisableClientState(GL_TEXTURE_COORD_ARRAY); +#endif + } + +#ifndef GL_ES + glPopAttrib(); +#else + glDisable(GL_BLEND); +#endif + + if(glBindBuffer && vbo) { + glBindBuffer(GL_ARRAY_BUFFER, vbo); + } + + num_quads = 0; +} +#else + +/* no-opengl build, define all public gl functions as stubs */ +void dtx_target_opengl(void) {} +int dtx_gl_setopt(enum dtx_option opt, int val) { return -1; } +int dtx_gl_getopt(enum dtx_option opt, int *val) { return -1; } + +static void set_glyphmap_texture_gl(struct dtx_glyphmap *gmap) {} + +#endif /* !def NO_OPENGL */ + +static int dtx_draw_init(void) +{ + if(qbuf) { + return 0; /* already initialized */ + } + +#ifndef NO_OPENGL + dtx_gl_init(); +#endif + + if(!(qbuf = malloc(QBUF_SZ * sizeof *qbuf))) { + return -1; + } + num_quads = 0; + + atexit(cleanup); + return 0; +} + +static void cleanup(void) +{ + free(qbuf); +} + + +void dtx_target_user(dtx_user_draw_func func, void *cls) +{ + dtx_draw_init(); + + user_draw_func = func; + user_cls = cls; + + dtx_drawchar = drawchar; + dtx_drawflush = flush_user; +} + +void dtx_glyph(int code) +{ + struct dtx_glyphmap *gmap; + + if(!dtx_font || !(gmap = dtx_get_font_glyphmap(dtx_font, dtx_font_sz, code))) { + return; + } + set_glyphmap_texture(gmap); + + add_glyph(gmap->glyphs + code - gmap->cstart, 0, 0); + dtx_flush(); +} + +static void set_glyphmap_texture(struct dtx_glyphmap *gmap) +{ + if(!user_draw_func) { + set_glyphmap_texture_gl(gmap); + } + + if(cur_gmap && gmap != cur_gmap) { + dtx_flush(); + } + cur_gmap = gmap; +} + +static const char *drawchar(const char *str, float *pos_x, float *pos_y, int *should_flush) +{ + struct dtx_glyphmap *gmap; + float px, py; + int code = dtx_utf8_char_code(str); + str = dtx_utf8_next_char((char*)str); + + if(dtx_buf_mode == DTX_LBF && code == '\n') { + *should_flush = 1; + } + + px = *pos_x; + py = *pos_y; + + if((gmap = dtx_proc_char(code, pos_x, pos_y))) { + int idx = code - gmap->cstart; + + set_glyphmap_texture(gmap); + add_glyph(gmap->glyphs + idx, px, py); + } + return str; +} + +static void qvertex(struct dtx_vertex *v, float x, float y, float s, float t) +{ + v->x = x; + v->y = y; + v->s = s; + v->t = t; +} + +static void add_glyph(struct glyph *g, float x, float y) +{ + struct quad *qptr = qbuf + num_quads; + + x -= g->orig_x; + y -= g->orig_y; + + qvertex(qptr->v + 0, x, y, g->nx, g->ny + g->nheight); + qvertex(qptr->v + 1, x + g->width, y, g->nx + g->nwidth, g->ny + g->nheight); + qvertex(qptr->v + 2, x + g->width, y + g->height, g->nx + g->nwidth, g->ny); + + qvertex(qptr->v + 3, x, y, g->nx, g->ny + g->nheight); + qvertex(qptr->v + 4, x + g->width, y + g->height, g->nx + g->nwidth, g->ny); + qvertex(qptr->v + 5, x, y + g->height, g->nx, g->ny); + + if(++num_quads >= QBUF_SZ) { + dtx_flush(); + } +} + +static void flush_user(void) +{ + struct dtx_pixmap pixmap; + + if(!num_quads || !user_draw_func || !cur_gmap) { + return; + } + + pixmap.pixels = cur_gmap->pixels; + pixmap.width = cur_gmap->xsz; + pixmap.height = cur_gmap->ysz; + pixmap.udata = cur_gmap->udata; + + user_draw_func((struct dtx_vertex*)qbuf, num_quads * 6, &pixmap, user_cls); + cur_gmap->udata = pixmap.udata; + + num_quads = 0; +} diff --git a/libs/drawtext/src/drawrast.c b/libs/drawtext/src/drawrast.c new file mode 100644 index 0000000..66543a5 --- /dev/null +++ b/libs/drawtext/src/drawrast.c @@ -0,0 +1,199 @@ +/* +libdrawtext - a simple library for fast text rendering in OpenGL +Copyright (C) 2011-2016 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see . +*/ +#include +#include +#include +#include "drawtext.h" +#include "drawtext_impl.h" + +static const char *drawchar(const char *str, float *xpos, float *ypos, int *should_flush); +static void flush(void); +static void draw_glyph(struct glyph *g, float x, float y); + +static unsigned char *fb_pixels; +static int fb_width, fb_height; +static struct dtx_glyphmap *gmap; +static int threshold = -1; +static int use_alpha; + +void dtx_target_raster(unsigned char *pixels, int width, int height) +{ + fb_pixels = pixels; + fb_width = width; + fb_height = height; + dtx_drawchar = drawchar; + dtx_drawflush = flush; +} + +int dtx_rast_setopt(enum dtx_option opt, int val) +{ + switch(opt) { + case DTX_RASTER_THRESHOLD: + threshold = val; + break; + case DTX_RASTER_BLEND: + use_alpha = val; + break; + default: + return -1; + } + return 0; +} + +int dtx_rast_getopt(enum dtx_option opt, int *res) +{ + switch(opt) { + case DTX_RASTER_THRESHOLD: + *res = threshold; + break; + case DTX_RASTER_BLEND: + *res = use_alpha ? 1 : 0; + break; + default: + return -1; + } + return 0; +} + +static const char *drawchar(const char *str, float *xpos, float *ypos, int *should_flush) +{ + float px, py; + int code = dtx_utf8_char_code(str); + str = dtx_utf8_next_char((char*)str); + + *should_flush = 0; /* the raster renderer never buffers output */ + + px = *xpos; + py = *ypos; + + if((gmap = dtx_proc_char(code, xpos, ypos))) { + int idx = code - gmap->cstart; + draw_glyph(gmap->glyphs + idx, px, py); + } + return str; +} + +static void flush(void) +{ +} + +static void blit_opaque(unsigned char *dest, unsigned char *src, int xsz, int ysz) +{ + int i, j; + int *col = dtx_cur_color_int; + + for(i=0; ixsz; + } +} + +static void blit_thres(unsigned char *dest, unsigned char *src, int xsz, int ysz) +{ + int i, j; + int *col = dtx_cur_color_int; + + for(i=0; i threshold) { + *dest++ = col[0]; + *dest++ = col[1]; + *dest++ = col[2]; + *dest++ = col[3]; + } else { + dest += 4; + } + } + dest += (fb_width - xsz) * 4; + src += gmap->xsz; + } +} + +static void blit_blend(unsigned char *dest, unsigned char *src, int xsz, int ysz) +{ + int i, j, k; + int *col = dtx_cur_color_int; + + for(i=0; ixsz; + } +} + +static void draw_glyph(struct glyph *g, float x, float y) +{ + unsigned char *dest, *src; + int gx = (int)g->x; + int gy = (int)g->y; + int gwidth = (int)g->width; + int gheight = (int)g->height; + int ix = (int)(x - g->orig_x); + int iy = (int)(y - gheight + g->orig_y); + + if(ix >= fb_width || iy >= fb_height) + return; + + if(ix < 0) { + gwidth += ix; + gx -= ix; + ix = 0; + } + if(iy < 0) { + gheight += iy; + gy -= iy; + iy = 0; + } + if(ix + gwidth >= fb_width) { + gwidth = fb_width - ix; + } + if(iy + gheight >= fb_height) { + gheight = fb_height - iy; + } + + if(gwidth <= 0 || gheight <= 0) + return; + + dest = fb_pixels + (iy * fb_width + ix) * 4; + src = gmap->pixels + gy * gmap->xsz + gx; + + if(use_alpha) { + blit_blend(dest, src, gwidth, gheight); + } else if(threshold > 0) { + blit_thres(dest, src, gwidth, gheight); + } else { + blit_opaque(dest, src, gwidth, gheight); + } +} + diff --git a/libs/drawtext/src/drawtext.h b/libs/drawtext/src/drawtext.h new file mode 100644 index 0000000..d2d1913 --- /dev/null +++ b/libs/drawtext/src/drawtext.h @@ -0,0 +1,308 @@ +/* +libdrawtext - a simple library for fast text rendering in OpenGL +Copyright (C) 2011-2019 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see . +*/ +#ifndef LIBDRAWTEXT_H_ +#define LIBDRAWTEXT_H_ + +#include +#include + +struct dtx_font; +struct dtx_glyphmap; + +/* draw buffering modes */ +enum { + DTX_NBF = 0,/* unbuffered */ + DTX_LBF, /* line buffered */ + DTX_FBF /* fully buffered */ +}; + +/* glyphmap resize filtering */ +enum { + DTX_NEAREST, + DTX_LINEAR +}; + +struct dtx_box { + float x, y; + float width, height; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/* Open a truetype/opentype/whatever font. + * + * If sz is non-zero, the default ASCII glyphmap at the requested point size is + * automatically created as well, and ready to use. + * + * To use other unicode ranges and different font sizes you must first call + * dtx_prepare or dtx_prepare_range before issuing any drawing calls, otherwise + * nothing will be rendered. + */ +struct dtx_font *dtx_open_font(const char *fname, int sz); +/* same as dtx_open_font, but open from a memory buffer instead of a file */ +struct dtx_font *dtx_open_font_mem(void *ptr, int memsz, int fontsz); +/* open a font by loading a precompiled glyphmap (see: dtx_save_glyphmap) + * this works even when compiled without freetype support + */ +struct dtx_font *dtx_open_font_glyphmap(const char *fname); +/* same as dtx_open_font_glyphmap, but open from a memory buffer instead of a file */ +struct dtx_font *dtx_open_font_glyphmap_mem(void *ptr, int memsz); +/* close a font opened by either of the above */ +void dtx_close_font(struct dtx_font *fnt); + +/* prepare an ASCII glyphmap for the specified font size */ +void dtx_prepare(struct dtx_font *fnt, int sz); +/* prepare an arbitrary unicode range glyphmap for the specified font size */ +void dtx_prepare_range(struct dtx_font *fnt, int sz, int cstart, int cend); + +/* convert all glyphmaps to distance fields for use with the distance field + * font rendering algorithm. This is a convenience function which calls + * dtx_calc_glyphmap_distfield and + * dtx_resize_glyphmap(..., scale_numer, scale_denom, DTX_LINEAR) for each + * glyphmap in this font. + */ +int dtx_calc_font_distfield(struct dtx_font *fnt, int scale_numer, int scale_denom); + +/* Finds the glyphmap that contains the specified character code and matches the requested size + * Returns null if it hasn't been created (you should call dtx_prepare/dtx_prepare_range). + */ +struct dtx_glyphmap *dtx_get_font_glyphmap(struct dtx_font *fnt, int sz, int code); + +/* Finds the glyphmap that contains the specified unicode range and matches the requested font size + * Will automatically generate one if it can't find it. + */ +struct dtx_glyphmap *dtx_get_font_glyphmap_range(struct dtx_font *fnt, int sz, int cstart, int cend); + +/* returns the number of glyphmaps in this font */ +int dtx_get_num_glyphmaps(struct dtx_font *fnt); +/* returns the Nth glyphmap of this font */ +struct dtx_glyphmap *dtx_get_glyphmap(struct dtx_font *fnt, int idx); + +/* Creates and returns a glyphmap for a particular unicode range and font size. + * The generated glyphmap is added to the font's list of glyphmaps. + */ +struct dtx_glyphmap *dtx_create_glyphmap_range(struct dtx_font *fnt, int sz, int cstart, int cend); +/* free a glyphmap */ +void dtx_free_glyphmap(struct dtx_glyphmap *gmap); + +/* converts a glyphmap to a distance field glyphmap, for use with the distance + * field font rendering algorithm. + * + * It is recommended to use a fairly large font size glyphmap for this, and + * then shrink the resulting distance field glyphmap as needed, with + * dtx_resize_glyphmap + */ +int dtx_calc_glyphmap_distfield(struct dtx_glyphmap *gmap); + +/* resize a glyphmap by the provided scale factor fraction snum/sdenom + * in order to maintain the power of 2 invariant, scaling fractions are only + * allowed to be of the form 1/x or x/1, where x is a power of 2 + */ +int dtx_resize_glyphmap(struct dtx_glyphmap *gmap, int snum, int sdenom, int filter); + +/* returns a pointer to the raster image of a glyphmap (1 byte per pixel grayscale) */ +unsigned char *dtx_get_glyphmap_image(struct dtx_glyphmap *gmap); +/* returns the width of the glyphmap image in pixels */ +int dtx_get_glyphmap_width(struct dtx_glyphmap *gmap); +/* returns the height of the glyphmap image in pixels */ +int dtx_get_glyphmap_height(struct dtx_glyphmap *gmap); +/* returns the point size represented by this glyphmap */ +int dtx_get_glyphmap_ptsize(struct dtx_glyphmap *gmap); + +/* The following functions can be used even when the library is compiled without + * freetype support. + */ +struct dtx_glyphmap *dtx_load_glyphmap(const char *fname); +struct dtx_glyphmap *dtx_load_glyphmap_stream(FILE *fp); +struct dtx_glyphmap *dtx_load_glyphmap_mem(void *ptr, int memsz); +int dtx_save_glyphmap(const char *fname, const struct dtx_glyphmap *gmap); +int dtx_save_glyphmap_stream(FILE *fp, const struct dtx_glyphmap *gmap); + +/* adds a glyphmap to a font */ +void dtx_add_glyphmap(struct dtx_font *fnt, struct dtx_glyphmap *gmap); + + +/* ---- options and flags ---- */ +enum dtx_option { + /* options for the OpenGL renderer */ + DTX_GL_ATTR_VERTEX, /* vertex attribute location (default: -1 for standard gl_Vertex) */ + DTX_GL_ATTR_TEXCOORD, /* texture uv attribute location (default: -1 for gl_MultiTexCoord0) */ + DTX_GL_ATTR_COLOR, /* color attribute location (default: -1 for gl_Color) */ + /* options for the raster renderer */ + DTX_RASTER_THRESHOLD, /* opaque/transparent threshold (default: -1. fully opaque glyphs) */ + DTX_RASTER_BLEND, /* glyph alpha blending (0 or 1) (default: 0 (off)) */ + + /* generic options */ + DTX_PADDING = 128, /* padding between glyphs in pixels (default: 8) */ + DTX_SAVE_PPM, /* let dtx_save_glyphmap* save PPM instead of PGM (0 or 1) (default: 0 (PGM)) */ + + DTX_FORCE_32BIT_ENUM = 0x7fffffff /* this is not a valid option */ +}; + +void dtx_set(enum dtx_option opt, int val); +int dtx_get(enum dtx_option opt); + + +/* ---- rendering ---- */ + +/* the dtx_target_ functions select which rendering mode to use. + * default: opengl + */ +void dtx_target_opengl(void); +/* pixels are expected to be RGBA ordered bytes, 4 per pixel + * text is rendered with pre-multiplied alpha + */ +void dtx_target_raster(unsigned char *pixels, int width, int height); + + +/* data structures passed to user-supplied draw callback */ +struct dtx_vertex { float x, y, s, t; }; +struct dtx_pixmap { + unsigned char *pixels; /* pixel buffer pointer (8 bits per pixel) */ + int width, height; /* dimensions of the pixel buffer */ + void *udata; /* user-supplied pointer to data associated with this + * pixmap. On the first callback invocation this pointer + * will be null. The user may set it to associate any extra + * data to this pixmap (such as texture structures or + * identifiers). Libdrawtext will never modify this pointer. + */ +}; + +/* user-defined glyph drawing callback type (set with dtx_target_user) + * It's called when the output buffer is flushed, with a pointer to the vertex + * buffer that needs to be drawn (every 3 vertices forming a triangle), the + * number of vertices in the buffer, and a pointer to the current glyphmap + * atlas pixmap (see struct dtx_pixmap above). + */ +typedef void (*dtx_user_draw_func)(struct dtx_vertex *v, int vcount, + struct dtx_pixmap *pixmap, void *cls); + +/* set user-supplied draw callback and optional closure pointer, which will + * be passed unchanged as the last argument on every invocation of the draw + * callback. + */ +void dtx_target_user(dtx_user_draw_func drawfunc, void *cls); + + +/* position of the origin of the first character to be printed */ +void dtx_position(float x, float y); +/* TODO currently only used by the raster renderer, implement in gl too */ +void dtx_color(float r, float g, float b, float a); + +/* before drawing anything this function must set the font to use */ +void dtx_use_font(struct dtx_font *fnt, int sz); + +/* sets the buffering mode + * - DTX_NBUF: every call to dtx_string gets rendered immediately. + * - DTX_LBUF: renders when the buffer is full or the string contains a newline. + * - DTX_FBUF: renders only when the buffer is full (you must call dtx_flush explicitly). + */ +void dtx_draw_buffering(int mode); + +/* Sets the vertex attribute indices to use for passing vertex and texture coordinate + * data. By default both are -1 which means you don't have to use a shader, and if you + * do they are accessible through gl_Vertex and gl_MultiTexCoord0, as usual. + * + * NOTE: If you are using OpenGL ES 2.x or OpenGL >= 3.1 core (non-compatibility) + * context you must specify valid attribute indices. + * + * NOTE2: equivalent to: + * dtx_set(DTX_GL_ATTR_VERTEX, vert_attr); + * dtx_set(DTX_GL_ATTR_TEXCOORD, tex_attr); + */ +void dtx_vertex_attribs(int vert_attr, int tex_attr); + +/* draws a single glyph at the origin */ +void dtx_glyph(int code); +/* draws a utf-8 string starting at the origin. \n \r and \t are handled appropriately. */ +void dtx_string(const char *str); +void dtx_substring(const char *str, int start, int end); + +void dtx_printf(const char *fmt, ...); + +/* render any pending glyphs (see dtx_draw_buffering) */ +void dtx_flush(void); + + +/* ---- encodings ---- */ + +/* returns a pointer to the next character in a utf-8 stream */ +char *dtx_utf8_next_char(char *str); + +/* returns a pointer to the previous character in a utf-8 stream */ +char *dtx_utf8_prev_char(char *ptr, char *first); + +/* returns the unicode character codepoint of the utf-8 character starting at str */ +int dtx_utf8_char_code(const char *str); + +/* returns the number of bytes of the utf-8 character starting at str */ +int dtx_utf8_nbytes(const char *str); + +/* returns the number of utf-8 characters in a zero-terminated utf-8 string */ +int dtx_utf8_char_count(const char *str); + +/* returns the number of utf-8 characters in the next N bytes starting from str */ +int dtx_utf8_char_count_range(const char *str, int nbytes); + +/* Converts a unicode code-point to a utf-8 character by filling in the buffer + * passed at the second argument, and returns the number of bytes taken by that + * utf-8 character. + * It's valid to pass a null buffer pointer, in which case only the byte count is + * returned (useful to figure out how much memory to allocate for a buffer). + */ +size_t dtx_utf8_from_char_code(int code, char *buf); + +/* Converts a unicode utf-16 wchar_t string to utf-8, filling in the buffer passed + * at the second argument. Returns the size of the resulting string in bytes. + * + * It's valid to pass a null buffer pointer, in which case only the size gets + * calculated and returned, which is useful for figuring out how much memory to + * allocate for the utf-8 buffer. + */ +size_t dtx_utf8_from_string(const wchar_t *str, char *buf); + + +/* ---- metrics ---- */ +float dtx_line_height(void); +float dtx_baseline(void); + +/* rendered dimensions of a single glyph */ +void dtx_glyph_box(int code, struct dtx_box *box); +float dtx_glyph_width(int code); +float dtx_glyph_height(int code); + +/* rendered dimensions of a string */ +void dtx_string_box(const char *str, struct dtx_box *box); +void dtx_substring_box(const char *str, int start, int end, struct dtx_box *box); +float dtx_string_width(const char *str); +float dtx_string_height(const char *str); + +/* returns the horizontal position of the n-th character of the rendered string + * (useful for placing cursors) + */ +float dtx_char_pos(const char *str, int n); + +int dtx_char_at_pt(const char *str, float pt); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBDRAWTEXT_H_ */ diff --git a/libs/drawtext/src/drawtext_impl.h b/libs/drawtext/src/drawtext_impl.h new file mode 100644 index 0000000..31042e3 --- /dev/null +++ b/libs/drawtext/src/drawtext_impl.h @@ -0,0 +1,94 @@ +/* +libdrawtext - a simple library for fast text rendering in OpenGL +Copyright (C) 2011-2016 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see . +*/ +#ifndef DRAWTEXT_IMPL_H_ +#define DRAWTEXT_IMPL_H_ + +#include "drawtext.h" + +struct glyph { + int code; + float x, y, width, height; + /* normalized coords [0, 1] */ + float nx, ny, nwidth, nheight; + float orig_x, orig_y; + float advance; + struct glyph *next; +}; + +struct dtx_glyphmap { + int ptsize; + + int xsz, ysz; + unsigned int xsz_shift; + unsigned char *pixels; + unsigned int tex; + int tex_valid; + void *udata; + + int cstart, cend; /* character range */ + int crange; + + float line_advance; + float baseline; + + struct glyph *glyphs; + struct dtx_glyphmap *next; +}; + +struct dtx_font { + /* freetype FT_Face */ + void *face; + + /* list of glyphmaps */ + struct dtx_glyphmap *gmaps; + + /* last returned glyphmap (cache) */ + struct dtx_glyphmap *last_gmap; +}; + +#ifndef COMMON +#define COMMON extern +#endif + +COMMON struct dtx_font *dtx_font; +COMMON int dtx_font_sz; +COMMON int dtx_buf_mode; /* DTX_NBF is 0 */ +COMMON float dtx_cur_color[4]; +COMMON int dtx_cur_color_int[4]; +COMMON float dtx_cur_offset[2]; + +#define fperror(str) \ + fprintf(stderr, "%s: %s: %s\n", __func__, (str), strerror(errno)) + +#ifdef _MSC_VER +#define __func__ __FUNCTION__ +#endif + +/* returns zero if it should NOT be printed and modifies xpos/ypos */ +/* implemented in font.c */ +struct dtx_glyphmap *dtx_proc_char(int code, float *xpos, float *ypos); + +COMMON const char *(*dtx_drawchar)(const char*, float*, float*, int*); +COMMON void (*dtx_drawflush)(void); + +int dtx_gl_setopt(enum dtx_option opt, int val); +int dtx_gl_getopt(enum dtx_option opt, int *ret); +int dtx_rast_setopt(enum dtx_option opt, int val); +int dtx_rast_getopt(enum dtx_option opt, int *ret); + +#endif /* DRAWTEXT_IMPL_H_ */ diff --git a/libs/drawtext/src/font.c b/libs/drawtext/src/font.c new file mode 100644 index 0000000..7dbde2c --- /dev/null +++ b/libs/drawtext/src/font.c @@ -0,0 +1,1330 @@ +/* +libdrawtext - a simple library for fast text rendering in OpenGL +Copyright (C) 2011-2018 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see . +*/ +#ifndef NO_FREETYPE +#define USE_FREETYPE +#endif + +#define COMMON + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef USE_FREETYPE +#include +#include FT_FREETYPE_H +#endif +#include "drawtext.h" +#include "drawtext_impl.h" +#include "tpool.h" + +struct io { + void *data; + int size; + int (*readchar)(struct io*); + void *(*readline)(void *buf, int bsz, struct io*); +}; + +#define FTSZ_TO_PIXELS(x) ((x) / 64) +#define MAX_IMG_SIZE 8192 + +static int opt_padding = 8; +static int opt_save_ppm; + +static struct dtx_glyphmap *load_glyphmap(struct io *io); + +#ifdef USE_FREETYPE +static int init_freetype(void); +static void cleanup(void); + +static int calc_best_size(int total_width, int max_gwidth, int max_gheight, + int padding, int pow2, int *imgw, int *imgh); +static int next_pow2(int x); + +static FT_Library ft; + + +static int init_done; + +static int init_freetype(void) +{ + if(!init_done) { + if(FT_Init_FreeType(&ft) != 0) { + return -1; + } + atexit(cleanup); + init_done = 1; + } + return 0; +} + +static void cleanup(void) +{ + if(init_done) { + FT_Done_FreeType(ft); + } +} +#endif /* USE_FREETYPE */ + +static int find_pow2(int x); + +struct dtx_font *dtx_open_font(const char *fname, int sz) +{ + struct dtx_font *fnt = 0; + +#ifdef USE_FREETYPE + init_freetype(); + + if(!(fnt = calloc(1, sizeof *fnt))) { + fperror("failed to allocate font structure"); + return 0; + } + + if(FT_New_Face(ft, fname, 0, (FT_Face*)&fnt->face) != 0) { + fprintf(stderr, "failed to open font file: %s\n", fname); + free(fnt); + return 0; + } + + /* pre-create the extended ASCII range glyphmap */ + if(sz) { + dtx_prepare_range(fnt, sz, 0, 256); + + if(!dtx_font) { + dtx_use_font(fnt, sz); + } + } +#else + fprintf(stderr, "ignoring call to dtx_open_font: not compiled with freetype support!\n"); +#endif + + return fnt; +} + +struct dtx_font *dtx_open_font_mem(void *ptr, int memsz, int fontsz) +{ + struct dtx_font *fnt = 0; + +#ifdef USE_FREETYPE + FT_Open_Args args; + + init_freetype(); + + if(!(fnt = calloc(1, sizeof *fnt))) { + fperror("failed to allocate font structure"); + return 0; + } + + memset(&args, 0, sizeof args); + args.flags = FT_OPEN_MEMORY; + args.memory_base = ptr; + args.memory_size = memsz; + + if(FT_Open_Face(ft, &args, 0, (FT_Face*)&fnt->face) != 0) { + fprintf(stderr, "failed to open font from memory\n"); + free(fnt); + return 0; + } + + /* pre-create the extended ASCII range glyphmap */ + if(fontsz) { + dtx_prepare_range(fnt, fontsz, 0, 256); + + if(!dtx_font) { + dtx_use_font(fnt, fontsz); + } + } +#else + fprintf(stderr, "ignoring call to dtx_open_font_mem: not compiled with freetype support!\n"); +#endif + + return fnt; +} + +struct dtx_font *dtx_open_font_glyphmap(const char *fname) +{ + struct dtx_font *fnt; + struct dtx_glyphmap *gmap; + + if(!(fnt = calloc(1, sizeof *fnt))) { + fperror("failed to allocate font structure"); + return 0; + } + + if(fname) { + if(!(gmap = dtx_load_glyphmap(fname))) { + free(fnt); + return 0; + } + + dtx_add_glyphmap(fnt, gmap); + + if(!dtx_font) { + dtx_use_font(fnt, gmap->ptsize); + } + } + return fnt; +} + +struct dtx_font *dtx_open_font_glyphmap_mem(void *ptr, int memsz) +{ + struct dtx_font *fnt; + struct dtx_glyphmap *gmap; + + if(!(fnt = calloc(1, sizeof *fnt))) { + fperror("failed to allocate font structure"); + return 0; + } + + if(!(gmap = dtx_load_glyphmap_mem(ptr, memsz))) { + free(fnt); + return 0; + } + + dtx_add_glyphmap(fnt, gmap); + + if(!dtx_font) { + dtx_use_font(fnt, gmap->ptsize); + } + return fnt; +} + +void dtx_close_font(struct dtx_font *fnt) +{ + if(!fnt) return; + +#ifdef USE_FREETYPE + FT_Done_Face(fnt->face); +#endif + + /* destroy the glyphmaps */ + while(fnt->gmaps) { + void *tmp = fnt->gmaps; + fnt->gmaps = fnt->gmaps->next; + dtx_free_glyphmap(tmp); + } + + free(fnt); +} + +void dtx_prepare(struct dtx_font *fnt, int sz) +{ + if(!dtx_get_font_glyphmap_range(fnt, sz, 0, 256)) { + fprintf(stderr, "%s: failed (sz: %d, range: 0-255 [ascii])\n", __func__, sz); + } +} + +void dtx_prepare_range(struct dtx_font *fnt, int sz, int cstart, int cend) +{ + if(!dtx_get_font_glyphmap_range(fnt, sz, cstart, cend)) { + fprintf(stderr, "%s: failed (sz: %d, range: %d-%d)\n", __func__, sz, cstart, cend); + } +} + +int dtx_calc_font_distfield(struct dtx_font *fnt, int scale_numer, int scale_denom) +{ + struct dtx_glyphmap *gm = fnt->gmaps; + while(gm) { + if(dtx_calc_glyphmap_distfield(gm) == -1) { + fprintf(stderr, "%s failed to create distfield glyphmap\n", __func__); + return -1; + } + + if(dtx_resize_glyphmap(gm, scale_numer, scale_denom, DTX_LINEAR) == -1) { + fprintf(stderr, "%s: failed to resize glyhphmap during distfield conversion\n", __func__); + } + gm->tex_valid = 0; + gm = gm->next; + } + return 0; +} + +struct dtx_glyphmap *dtx_get_font_glyphmap(struct dtx_font *fnt, int sz, int code) +{ + struct dtx_glyphmap *gm; + + /* check to see if the last we've given out fits the bill */ + if(fnt->last_gmap && code >= fnt->last_gmap->cstart && code < fnt->last_gmap->cend && fnt->last_gmap->ptsize == sz) { + return fnt->last_gmap; + } + + /* otherwise search for the appropriate glyphmap */ + gm = fnt->gmaps; + while(gm) { + if(code >= gm->cstart && code < gm->cend && sz == gm->ptsize) { + fnt->last_gmap = gm; + return gm; + } + gm = gm->next; + } + return 0; +} + +struct dtx_glyphmap *dtx_get_font_glyphmap_range(struct dtx_font *fnt, int sz, int cstart, int cend) +{ + struct dtx_glyphmap *gm; + + /* search the available glyphmaps to see if we've got one that includes + * the requested range + */ + gm = fnt->gmaps; + while(gm) { + if(gm->cstart <= cstart && gm->cend >= cend && gm->ptsize == sz) { + return gm; + } + gm = gm->next; + } + + /* not found, create one and add it to the list */ + if(!(gm = dtx_create_glyphmap_range(fnt, sz, cstart, cend))) { + return 0; + } + return gm; +} + +int dtx_get_num_glyphmaps(struct dtx_font *fnt) +{ + int count = 0; + struct dtx_glyphmap *gm = fnt->gmaps; + while(gm) { + ++count; + gm = gm->next; + } + return count; +} + +struct dtx_glyphmap *dtx_get_glyphmap(struct dtx_font *fnt, int idx) +{ + struct dtx_glyphmap *gm = fnt->gmaps; + while(gm && idx--) { + gm = gm->next; + } + return gm; +} + +struct dtx_glyphmap *dtx_create_glyphmap_range(struct dtx_font *fnt, int sz, int cstart, int cend) +{ + struct dtx_glyphmap *gmap = 0; + +#ifdef USE_FREETYPE + FT_Face face = fnt->face; + int i, j; + int gx, gy; + int total_width, max_width, max_height; + int half_pad = opt_padding / 2; + + FT_Set_Char_Size(fnt->face, 0, sz * 64, 72, 72); + + if(!(gmap = calloc(1, sizeof *gmap))) { + return 0; + } + + gmap->ptsize = sz; + gmap->cstart = cstart; + gmap->cend = cend; + gmap->crange = cend - cstart; + gmap->line_advance = FTSZ_TO_PIXELS((float)face->size->metrics.height); + gmap->baseline = -FTSZ_TO_PIXELS((float)face->size->metrics.descender); + + if(!(gmap->glyphs = malloc(gmap->crange * sizeof *gmap->glyphs))) { + free(gmap); + return 0; + } + + total_width = opt_padding; + max_width = max_height = 0; + + for(i=0; icrange; i++) { + int w, h; + + FT_Load_Char(face, i + cstart, 0); + w = FTSZ_TO_PIXELS(face->glyph->metrics.width); + h = FTSZ_TO_PIXELS(face->glyph->metrics.height); + + if(w > max_width) max_width = w; + if(h > max_height) max_height = h; + + total_width += w + opt_padding; + } + + if(calc_best_size(total_width, max_width, max_height, opt_padding, 1, &gmap->xsz, &gmap->ysz) == -1) { + free(gmap->glyphs); + free(gmap); + return 0; + } + gmap->xsz_shift = find_pow2(gmap->xsz); + + if(!(gmap->pixels = malloc(gmap->xsz * gmap->ysz))) { + free(gmap->glyphs); + free(gmap); + return 0; + } + memset(gmap->pixels, 0, gmap->xsz * gmap->ysz); + + gx = opt_padding; + gy = opt_padding; + + for(i=0; icrange; i++) { + float gwidth, gheight; + unsigned char *src, *dst; + FT_GlyphSlot glyph; + + FT_Load_Char(face, i + cstart, FT_LOAD_RENDER); + glyph = face->glyph; + gwidth = FTSZ_TO_PIXELS((float)glyph->metrics.width); + gheight = FTSZ_TO_PIXELS((float)glyph->metrics.height); + + if(gx > gmap->xsz - gwidth - opt_padding) { + gx = opt_padding; + gy += max_height + opt_padding; + } + + src = glyph->bitmap.buffer; + dst = gmap->pixels + (gy << gmap->xsz_shift) + gx; + + for(j=0; j<(int)glyph->bitmap.rows; j++) { + memcpy(dst, src, glyph->bitmap.width); + dst += gmap->xsz; + src += glyph->bitmap.pitch; + } + + gmap->glyphs[i].code = i; + gmap->glyphs[i].x = gx - half_pad; + gmap->glyphs[i].y = gy - half_pad; + gmap->glyphs[i].width = gwidth + half_pad * 2; + gmap->glyphs[i].height = gheight + half_pad * 2; + gmap->glyphs[i].orig_x = -FTSZ_TO_PIXELS((float)glyph->metrics.horiBearingX) + 1; + gmap->glyphs[i].orig_y = FTSZ_TO_PIXELS((float)glyph->metrics.height - glyph->metrics.horiBearingY) + 1; + gmap->glyphs[i].advance = FTSZ_TO_PIXELS((float)glyph->metrics.horiAdvance); + /* also precalc normalized */ + gmap->glyphs[i].nx = (float)gmap->glyphs[i].x / (float)gmap->xsz; + gmap->glyphs[i].ny = (float)gmap->glyphs[i].y / (float)gmap->ysz; + gmap->glyphs[i].nwidth = (float)gmap->glyphs[i].width / (float)gmap->xsz; + gmap->glyphs[i].nheight = (float)gmap->glyphs[i].height / (float)gmap->ysz; + + gx += gwidth + opt_padding; + } + + /* add it to the glyphmaps list of the font */ + dtx_add_glyphmap(fnt, gmap); +#endif /* USE_FREETYPE */ + + return gmap; +} + +void dtx_free_glyphmap(struct dtx_glyphmap *gmap) +{ + if(gmap) { + free(gmap->pixels); + free(gmap->glyphs); + free(gmap); + } +} + +#define CHECK_BOUNDS(gm, x, y) ((x) >= 0 && (x) < (gm)->xsz && (y) >= 0 && (y) < (gm)->ysz) +#define GET_PIXEL(gm, x, y) ((gm)->pixels[((y) << (gm)->xsz_shift) + (x)]) + +static int calc_distance(struct dtx_glyphmap *gmap, int x, int y, int max_dist) +{ + int i, j, startx, starty, endx, endy, px, py; + int bwidth, bheight; + int min_distsq = INT_MAX; + unsigned char cpix = GET_PIXEL(gmap, x, y); + int dist; + + if(max_dist > 128) max_dist = 128; + + startx = x >= max_dist ? x - max_dist : 0; + starty = y >= max_dist ? y - max_dist : 0; + endx = x + max_dist < gmap->xsz ? x + max_dist : gmap->xsz - 1; + endy = y + max_dist < gmap->ysz ? y + max_dist : gmap->ysz - 1; + + /* try the cardinal directions first to find the search bounding box */ + for(i=0; i<4; i++) { + int max_dist = x - startx; + for(j=0; j 127) dist = 127; + + return cpix ? dist + 128 : 127 - dist; +} + +struct distcalc_data { + struct dtx_glyphmap *gmap; + int scanline; + unsigned char *pixels; +}; + +static void distcalc_func(void *cls) +{ + int i; + struct distcalc_data *data = cls; + struct dtx_glyphmap *gmap = data->gmap; + + printf("scanline %d of %d\n", data->scanline + 1, gmap->ysz); + for(i=0; ixsz; i++) { + *data->pixels++ = calc_distance(gmap, i, data->scanline, 64); + } +} + +int dtx_calc_glyphmap_distfield(struct dtx_glyphmap *gmap) +{ + int i, num_pixels = gmap->xsz * gmap->ysz; + unsigned char *new_pixels; + unsigned char *dptr; +#ifdef USE_THREADS + struct dtx_thread_pool *tpool = 0; + struct distcalc_data *data = 0; +#endif + + /* first quantize the glyphmap to 1bit */ + dptr = gmap->pixels; + for(i=0; ixsz, gmap->ysz); + return -1; + } + dptr = new_pixels; + +#ifdef USE_THREADS + tpool = dtx_tpool_create(0); + data = malloc(sizeof *data * gmap->ysz); + + if(tpool) { + for(i=0; iysz; i++) { + data[i].gmap = gmap; + data[i].scanline = i; + data[i].pixels = new_pixels + (i << gmap->xsz_shift); + dtx_tpool_enqueue(tpool, data + i, distcalc_func, 0); + } + dtx_tpool_wait(tpool); + dtx_tpool_destroy(tpool); + free(data); + } else +#endif /* USE_THREADS */ + { + for(i=0; iysz; i++) { + struct distcalc_data d; + d.gmap = gmap; + d.scanline = i; + d.pixels = new_pixels + (i << gmap->xsz_shift); + distcalc_func(&d); + } + } + + free(gmap->pixels); + gmap->pixels = new_pixels; + return 0; +} + +static unsigned char sample_area(struct dtx_glyphmap *gm, float x, float y, float area) +{ + int i, j; + int ksz = (int)(area + 0.5); + int half_ksz = ksz / 2; + + int sum = 0, nsamples = 0; + + for(i=0; i= gm->xsz || sy < 0 || sy >= gm->ysz) { + continue; + } + + sum += gm->pixels[(sy << gm->xsz_shift) + sx]; + ++nsamples; + } + } + + if(nsamples != 0) { + sum /= nsamples; + } + return sum > 255 ? 255 : sum; +} + +static unsigned char sample_pixel(struct dtx_glyphmap *gm, int x, int y) +{ + if(CHECK_BOUNDS(gm, x, y)) { + return gm->pixels[(y << gm->xsz_shift) + x]; + } + return 0; +} + +static int count_bits(int x) +{ + int i, n = 0; + for(i=0; i>= 1; + } + return n; +} + +int dtx_resize_glyphmap(struct dtx_glyphmap *gmap, int snum, int sdenom, int filter) +{ + int i, j, nxsz, nysz; + unsigned char *dptr, *new_pixels; + float scale, inv_scale, area; + + if(snum == sdenom) return 0; + + if((count_bits(snum) | count_bits(sdenom)) != 1) { + fprintf(stderr, "%s: invalid scale fraction %d/%d (not power of 2)\n", __func__, snum, sdenom); + return -1; + } + + /* normalize the fraction */ + if(snum > sdenom) { + snum /= sdenom; + sdenom /= sdenom; + } else { + snum /= snum; + sdenom /= snum; + } + + if(snum != 1 && sdenom != 1) { + fprintf(stderr, "%s: invalid scale fraction %d/%d (neither is 1)\n", __func__, snum, sdenom); + return -1; + } + + nxsz = snum * gmap->xsz / sdenom; + nysz = snum * gmap->ysz / sdenom; + + if(nxsz < 1 || nysz < 1) { + return -1; + } + + new_pixels = malloc(nxsz * nysz); + if(!new_pixels) { + fprintf(stderr, "%s: failed to allocate %dx%d pixel buffer\n", __func__, nxsz, nysz); + return -1; + } + + dptr = new_pixels; + + scale = (float)snum / (float)sdenom; + inv_scale = 1.0 / scale; + area = scale <= 1.0 ? inv_scale : 2.0; + + if(filter == DTX_NEAREST) { + /* no filtering, nearest neighbor */ + for(i=0; ipixels); + gmap->pixels = new_pixels; + gmap->xsz = nxsz; + gmap->ysz = nysz; + gmap->xsz_shift = find_pow2(nxsz); + + /* also scale all the metrics accordingly */ + for(i=0; icrange; i++) { + struct glyph *g = gmap->glyphs + i; + g->x *= scale; + g->y *= scale; + g->width *= scale; + g->height *= scale; + g->orig_x *= scale; + g->orig_y *= scale; + g->advance *= scale; + } + gmap->ptsize = snum * gmap->ptsize / sdenom; + gmap->line_advance *= scale; + return 0; +} + +unsigned char *dtx_get_glyphmap_pixels(struct dtx_glyphmap *gmap) +{ + return gmap->pixels; +} + +int dtx_get_glyphmap_width(struct dtx_glyphmap *gmap) +{ + return gmap->xsz; +} + +int dtx_get_glyphmap_height(struct dtx_glyphmap *gmap) +{ + return gmap->ysz; +} + +int dtx_get_glyphmap_ptsize(struct dtx_glyphmap *gmap) +{ + return gmap->ptsize; +} + +struct dtx_glyphmap *dtx_load_glyphmap(const char *fname) +{ + FILE *fp; + struct dtx_glyphmap *gmap; + + if(!(fp = fopen(fname, "rb"))) { + return 0; + } + gmap = dtx_load_glyphmap_stream(fp); + fclose(fp); + return gmap; +} + + +static int file_readchar(struct io *io) +{ + return fgetc(io->data); +} + +static void *file_readline(void *buf, int bsz, struct io *io) +{ + return fgets(buf, bsz, io->data); +} + +static int mem_readchar(struct io *io) +{ + char *p = io->data; + + if(io->size-- <= 0) { + return -1; + } + io->data = p + 1; + return *p; +} + +static void *mem_readline(void *buf, int bsz, struct io *io) +{ + int c; + char *ptr = buf; + + while(--bsz > 0 && (c = mem_readchar(io)) != -1) { + *ptr++ = c; + if(c == '\n') break; + } + *ptr = 0; + + return buf; +} + +struct dtx_glyphmap *dtx_load_glyphmap_stream(FILE *fp) +{ + struct io io; + io.data = fp; + io.readchar = file_readchar; + io.readline = file_readline; + return load_glyphmap(&io); +} + +struct dtx_glyphmap *dtx_load_glyphmap_mem(void *ptr, int memsz) +{ + struct io io; + io.data = ptr; + io.size = memsz; + io.readchar = mem_readchar; + io.readline = mem_readline; + return load_glyphmap(&io); +} + +static struct dtx_glyphmap *load_glyphmap(struct io *io) +{ + char buf[512]; + int hdr_lines = 0; + struct dtx_glyphmap *gmap; + struct glyph *glyphs = 0; + struct glyph *g; + int min_code = INT_MAX; + int max_code = INT_MIN; + int i, max_pixval = 255, num_pixels; + int greyscale = 0; + + if(!(gmap = calloc(1, sizeof *gmap))) { + fperror("failed to allocate glyphmap"); + return 0; + } + gmap->ptsize = -1; + gmap->line_advance = FLT_MIN; + + while(hdr_lines < 3) { + char *line = buf; + if(!io->readline(buf, sizeof buf, io)) { + fperror("unexpected end of file"); + goto err; + } + + while(isspace(*line)) { + line++; + } + + if(line[0] == '#') { + int c, res; + float x, y, xsz, ysz, orig_x, orig_y, adv, line_adv; + int ptsize; + + if((res = sscanf(line + 1, " size: %d\n", &ptsize)) == 1) { + gmap->ptsize = ptsize; + + } else if((res = sscanf(line + 1, " advance: %f\n", &line_adv)) == 1) { + gmap->line_advance = line_adv; + + } else if((res = sscanf(line + 1, " %d: %fx%f+%f+%f o:%f,%f adv:%f\n", + &c, &xsz, &ysz, &x, &y, &orig_x, &orig_y, &adv)) == 8) { + if(!(g = malloc(sizeof *g))) { + fperror("failed to allocate glyph"); + goto err; + } + g->code = c; + g->x = x; + g->y = y; + g->width = xsz; + g->height = ysz; + g->orig_x = orig_x; + g->orig_y = orig_y; + g->advance = adv; + /* normalized coordinates will be precalculated after everything is loaded */ + + g->next = glyphs; + glyphs = g; + + if(c < min_code) { + min_code = c; + } + if(c > max_code) { + max_code = c; + } + + } else { + fprintf(stderr, "%s: invalid glyph info line\n", __func__); + goto err; + } + + } else { + switch(hdr_lines) { + case 0: + if(line[0] != 'P' || !(line[1] == '6' || line[1] == '5')) { + fprintf(stderr, "%s: invalid file format (magic)\n", __func__); + goto err; + } + greyscale = line[1] == '5'; + break; + + case 1: + if(sscanf(line, "%d %d", &gmap->xsz, &gmap->ysz) != 2) { + fprintf(stderr, "%s: invalid file format (dim)\n", __func__); + goto err; + } + break; + + case 2: + { + char *endp; + max_pixval = strtol(line, &endp, 10); + if(endp == line) { + fprintf(stderr, "%s: invalid file format (maxval)\n", __func__); + goto err; + } + } + break; + + default: + break; /* can't happen */ + } + hdr_lines++; + } + } + + if(gmap->ptsize == -1 || gmap->line_advance == FLT_MIN) { + fprintf(stderr, "%s: invalid glyphmap, insufficient information in ppm comments\n", __func__); + goto err; + } + + /* precalculate normalized glyph coordinates */ + g = glyphs; + while(g) { + g->nx = g->x / gmap->xsz; + g->ny = g->y / gmap->ysz; + g->nwidth = g->width / gmap->xsz; + g->nheight = g->height / gmap->ysz; + g = g->next; + } + + num_pixels = gmap->xsz * gmap->ysz; + if(!(gmap->pixels = malloc(num_pixels))) { + fperror("failed to allocate pixels"); + goto err; + } + + for(i=0; ireadchar(io); + if(c == -1) { + fprintf(stderr, "unexpected end of file while reading pixels\n"); + goto err; + } + gmap->pixels[i] = 255 * c / max_pixval; + if(!greyscale) { + io->readchar(io); + io->readchar(io); + } + } + + gmap->xsz_shift = find_pow2(gmap->xsz); + gmap->cstart = min_code; + gmap->cend = max_code + 1; + gmap->crange = gmap->cend - gmap->cstart; + + if(!(gmap->glyphs = calloc(gmap->crange, sizeof *gmap->glyphs))) { + fperror("failed to allocate glyph info"); + goto err; + } + + while(glyphs) { + struct glyph *g = glyphs; + glyphs = glyphs->next; + + gmap->glyphs[g->code - gmap->cstart] = *g; + free(g); + } + return gmap; + +err: + dtx_free_glyphmap(gmap); + while(glyphs) { + void *tmp = glyphs; + glyphs = glyphs->next; + free(tmp); + } + return 0; +} + +int dtx_save_glyphmap(const char *fname, const struct dtx_glyphmap *gmap) +{ + FILE *fp; + int res; + + if(!(fp = fopen(fname, "wb"))) { + fprintf(stderr, "%s: failed to open file: %s: %s\n", __func__, fname, strerror(errno)); + return -1; + } + res = dtx_save_glyphmap_stream(fp, gmap); + fclose(fp); + return res; +} + +int dtx_save_glyphmap_stream(FILE *fp, const struct dtx_glyphmap *gmap) +{ + int i, num_pixels; + struct glyph *g = gmap->glyphs; + + fprintf(fp, "P%d\n%d %d\n", opt_save_ppm ? 6 : 5, gmap->xsz, gmap->ysz); + fprintf(fp, "# size: %d\n", gmap->ptsize); + fprintf(fp, "# advance: %g\n", gmap->line_advance); + for(i=0; icrange; i++) { + fprintf(fp, "# %d: %gx%g+%g+%g o:%g,%g adv:%g\n", g->code + gmap->cstart, + g->width, g->height, g->x, g->y, g->orig_x, g->orig_y, g->advance); + g++; + } + fprintf(fp, "255\n"); + + num_pixels = gmap->xsz * gmap->ysz; + for(i=0; ipixels[i]; + fputc(c, fp); + if(opt_save_ppm) { + fputc(c, fp); + fputc(c, fp); + } + } + return 0; +} + +void dtx_add_glyphmap(struct dtx_font *fnt, struct dtx_glyphmap *gmap) +{ + gmap->next = fnt->gmaps; + fnt->gmaps = gmap; +} + + +void dtx_set(enum dtx_option opt, int val) +{ + switch(opt) { + case DTX_PADDING: + opt_padding = val; + break; + + case DTX_SAVE_PPM: + opt_save_ppm = val; + break; + + default: + dtx_gl_setopt(opt, val); + dtx_rast_setopt(opt, val); + } +} + +int dtx_get(enum dtx_option opt) +{ + int val; + + switch(opt) { + case DTX_PADDING: + return opt_padding; + + case DTX_SAVE_PPM: + return opt_save_ppm; + + default: + break; + } + + if(dtx_gl_getopt(opt, &val) != -1) { + return val; + } + if(dtx_rast_getopt(opt, &val) != -1) { + return val; + } + return -1; +} + +void dtx_use_font(struct dtx_font *fnt, int sz) +{ + if(!dtx_drawchar) { + dtx_target_opengl(); + } + + dtx_font = fnt; + dtx_font_sz = sz; +} + +float dtx_line_height(void) +{ + struct dtx_glyphmap *gmap = dtx_get_glyphmap(dtx_font, 0); + + return gmap->line_advance; +} + +float dtx_baseline(void) +{ + struct dtx_glyphmap *gmap = dtx_get_glyphmap(dtx_font, 0); + + return gmap->baseline; +} + +void dtx_glyph_box(int code, struct dtx_box *box) +{ + int cidx; + struct dtx_glyphmap *gmap; + gmap = dtx_get_font_glyphmap(dtx_font, dtx_font_sz, code); + + cidx = code - gmap->cstart; + + box->x = gmap->glyphs[cidx].orig_x; + box->y = gmap->glyphs[cidx].orig_y; + box->width = gmap->glyphs[cidx].width; + box->height = gmap->glyphs[cidx].height; +} + +float dtx_glyph_width(int code) +{ + struct dtx_box box; + dtx_glyph_box(code, &box); + return box.width; +} + +float dtx_glyph_height(int code) +{ + struct dtx_box box; + dtx_glyph_box(code, &box); + return box.height; +} + +void dtx_string_box(const char *str, struct dtx_box *box) +{ + dtx_substring_box(str, 0, INT_MAX, box); +} + +void dtx_substring_box(const char *str, int start, int end, struct dtx_box *box) +{ + int code; + float pos_x = 0.0f, pos_y = 0.0f; + struct glyph *g = 0; + float x0, y0, x1, y1; + + x0 = y0 = FLT_MAX; + x1 = y1 = -FLT_MAX; + + /* skip start characters */ + while(*str && start > 0) { + str = dtx_utf8_next_char((char*)str); + --start; + --end; + } + + while(*str && --end >= 0) { + float px, py; + struct dtx_glyphmap *gmap; + + code = dtx_utf8_char_code(str); + str = dtx_utf8_next_char((char*)str); + + px = pos_x; + py = pos_y; + + if((gmap = dtx_proc_char(code, &pos_x, &pos_y))) { + g = gmap->glyphs + code - gmap->cstart; + + if(px + g->orig_x < x0) { + x0 = px + g->orig_x; + } + if(py - g->orig_y < y0) { + y0 = py - g->orig_y; + } + if(px + g->orig_x + g->width > x1) { + x1 = px + g->orig_x + g->width; + } + if(py - g->orig_y + g->height > y1) { + y1 = py - g->orig_y + g->height; + } + } + } + + box->x = x0; + box->y = y0; + box->width = x1 - x0; + box->height = y1 - y0; +} + +float dtx_string_width(const char *str) +{ + struct dtx_box box; + + dtx_string_box(str, &box); + return box.width; +} + +float dtx_string_height(const char *str) +{ + struct dtx_box box; + + dtx_string_box(str, &box); + return box.height; +} + +float dtx_char_pos(const char *str, int n) +{ + int i, code; + float pos = 0.0; + struct dtx_glyphmap *gmap; + + for(i=0; iglyphs[code - gmap->cstart].advance; + } + } + return pos; +} + +int dtx_char_at_pt(const char *str, float pt) +{ + int i; + float prev_pos = 0.0f, pos = 0.0f; + struct dtx_glyphmap *gmap; + + for(i=0; *str; i++) { + int code = dtx_utf8_char_code(str); + str = dtx_utf8_next_char((char*)str); + + if((gmap = dtx_get_font_glyphmap(dtx_font, dtx_font_sz, code))) { + pos += gmap->glyphs[code - gmap->cstart].advance; + + if(fabs(pt - prev_pos) < fabs(pt - pos)) { + break; + } + } + prev_pos = pos; + } + return i; +} + +struct dtx_glyphmap *dtx_proc_char(int code, float *xpos, float *ypos) +{ + struct dtx_glyphmap *gmap; + gmap = dtx_get_font_glyphmap(dtx_font, dtx_font_sz, code); + + switch(code) { + case '\n': + *xpos = 0.0; + if(gmap) { + *ypos -= gmap->line_advance; + } + return 0; + + case '\t': + if(gmap) { + *xpos = (fmod(*xpos, 4.0) + 4.0) * gmap->glyphs[0].advance; + } + return 0; + + case '\r': + *xpos = 0.0; + return 0; + + default: + break; + } + + if(gmap) { + *xpos += gmap->glyphs[code - gmap->cstart].advance; + } + return gmap; +} + +#ifdef USE_FREETYPE +static int calc_best_size(int total_width, int max_gwidth, int max_gheight, int padding, int pow2, int *imgw, int *imgh) +{ + int xsz, ysz, num_rows; + float aspect; + + /* the widest glyph won't fit in the maximum image size */ + if(max_gwidth > MAX_IMG_SIZE) { + return -1; + } + + for(xsz=2; xsz<=MAX_IMG_SIZE; xsz *= 2) { + num_rows = total_width / xsz + 1; + + /* assume worst case, all last glyphs will float to the next line + * so let's add extra rows for that. */ + num_rows += (padding + (max_gwidth + padding) * num_rows + xsz - 1) / xsz; + + ysz = num_rows * (max_gheight + padding) + padding; + if(ysz <= 0 || ysz > MAX_IMG_SIZE) continue; + + if(pow2) { + ysz = next_pow2(ysz); + } + aspect = (float)xsz / (float)ysz; + + if(aspect >= 1.0) { + break; + } + } + + if(xsz > MAX_IMG_SIZE || ysz > MAX_IMG_SIZE || ysz <= 0) { + return -1; + } + + *imgw = xsz; + *imgh = ysz; + return 0; +} + + +static int next_pow2(int x) +{ + x--; + x = (x >> 1) | x; + x = (x >> 2) | x; + x = (x >> 4) | x; + x = (x >> 8) | x; + x = (x >> 16) | x; + return x + 1; +} +#endif + +static int find_pow2(int x) +{ + int i; + for(i=0; i + * This code is public domain. + */ +#include +#include +#include + +#ifdef USE_THREADS +#include +#include +#include +#include "tpool.h" + +struct work_item { + void *data; + dtx_tpool_callback work, done; + struct work_item *next; +}; + +struct dtx_thread_pool { + pthread_t *threads; + int num_threads; + + int qsize; + struct work_item *workq, *workq_tail; + pthread_mutex_t workq_mutex; + pthread_cond_t workq_condvar; + + int nactive; /* number of active workers (not sleeping) */ + + pthread_cond_t done_condvar; + + int should_quit; + int in_batch; +}; + +static void *thread_func(void *args); + +struct dtx_thread_pool *dtx_tpool_create(int num_threads) +{ + int i; + struct dtx_thread_pool *tpool; + + if(!(tpool = calloc(1, sizeof *tpool))) { + return 0; + } + pthread_mutex_init(&tpool->workq_mutex, 0); + pthread_cond_init(&tpool->workq_condvar, 0); + pthread_cond_init(&tpool->done_condvar, 0); + + if(num_threads <= 0) { + num_threads = dtx_tpool_num_processors(); + } + tpool->num_threads = num_threads; + + if(!(tpool->threads = calloc(num_threads, sizeof *tpool->threads))) { + free(tpool); + return 0; + } + for(i=0; ithreads + i, 0, thread_func, tpool) == -1) { + tpool->threads[i] = 0; + dtx_tpool_destroy(tpool); + return 0; + } + } + return tpool; +} + +void dtx_tpool_destroy(struct dtx_thread_pool *tpool) +{ + int i; + if(!tpool) return; + + dtx_tpool_clear(tpool); + tpool->should_quit = 1; + + pthread_cond_broadcast(&tpool->workq_condvar); + + if(tpool->threads) { + printf("dtx_thread_pool: waiting for %d worker threads to stop ", tpool->num_threads); + fflush(stdout); + + for(i=0; inum_threads; i++) { + pthread_join(tpool->threads[i], 0); + putchar('.'); + fflush(stdout); + } + putchar('\n'); + free(tpool->threads); + } + + pthread_mutex_destroy(&tpool->workq_mutex); + pthread_cond_destroy(&tpool->workq_condvar); + pthread_cond_destroy(&tpool->done_condvar); +} + +void dtx_tpool_begin_batch(struct dtx_thread_pool *tpool) +{ + tpool->in_batch = 1; +} + +void dtx_tpool_end_batch(struct dtx_thread_pool *tpool) +{ + tpool->in_batch = 0; + pthread_cond_broadcast(&tpool->workq_condvar); +} + +int dtx_tpool_enqueue(struct dtx_thread_pool *tpool, void *data, + dtx_tpool_callback work_func, dtx_tpool_callback done_func) +{ + struct work_item *job; + + if(!(job = malloc(sizeof *job))) { + return -1; + } + job->work = work_func; + job->done = done_func; + job->data = data; + job->next = 0; + + pthread_mutex_lock(&tpool->workq_mutex); + if(tpool->workq) { + tpool->workq_tail->next = job; + tpool->workq_tail = job; + } else { + tpool->workq = tpool->workq_tail = job; + } + ++tpool->qsize; + pthread_mutex_unlock(&tpool->workq_mutex); + + if(!tpool->in_batch) { + pthread_cond_broadcast(&tpool->workq_condvar); + } + return 0; +} + +void dtx_tpool_clear(struct dtx_thread_pool *tpool) +{ + pthread_mutex_lock(&tpool->workq_mutex); + while(tpool->workq) { + void *tmp = tpool->workq; + tpool->workq = tpool->workq->next; + free(tmp); + } + tpool->workq = tpool->workq_tail = 0; + tpool->qsize = 0; + pthread_mutex_unlock(&tpool->workq_mutex); +} + +int dtx_tpool_queued_jobs(struct dtx_thread_pool *tpool) +{ + int res; + pthread_mutex_lock(&tpool->workq_mutex); + res = tpool->qsize; + pthread_mutex_unlock(&tpool->workq_mutex); + return res; +} + +int dtx_tpool_active_jobs(struct dtx_thread_pool *tpool) +{ + int res; + pthread_mutex_lock(&tpool->workq_mutex); + res = tpool->nactive; + pthread_mutex_unlock(&tpool->workq_mutex); + return res; +} + +int dtx_tpool_pending_jobs(struct dtx_thread_pool *tpool) +{ + int res; + pthread_mutex_lock(&tpool->workq_mutex); + res = tpool->qsize + tpool->nactive; + pthread_mutex_unlock(&tpool->workq_mutex); + return res; +} + +void dtx_tpool_wait(struct dtx_thread_pool *tpool) +{ + pthread_mutex_lock(&tpool->workq_mutex); + while(tpool->nactive || tpool->qsize) { + pthread_cond_wait(&tpool->done_condvar, &tpool->workq_mutex); + } + pthread_mutex_unlock(&tpool->workq_mutex); +} + +void dtx_tpool_wait_one(struct dtx_thread_pool *tpool) +{ + int cur_pending; + pthread_mutex_lock(&tpool->workq_mutex); + cur_pending = tpool->qsize + tpool->nactive; + if(cur_pending) { + while(tpool->qsize + tpool->nactive >= cur_pending) { + pthread_cond_wait(&tpool->done_condvar, &tpool->workq_mutex); + } + } + pthread_mutex_unlock(&tpool->workq_mutex); +} + +long dtx_tpool_timedwait(struct dtx_thread_pool *tpool, long timeout) +{ + long sec; + struct timespec tout_ts; + struct timeval tv0, tv; + gettimeofday(&tv0, 0); + + sec = timeout / 1000; + tout_ts.tv_nsec = tv0.tv_usec * 1000 + (timeout % 1000) * 1000000; + tout_ts.tv_sec = tv0.tv_sec + sec; + + pthread_mutex_lock(&tpool->workq_mutex); + while(tpool->nactive || tpool->qsize) { + if(pthread_cond_timedwait(&tpool->done_condvar, + &tpool->workq_mutex, &tout_ts) == ETIMEDOUT) { + break; + } + } + pthread_mutex_unlock(&tpool->workq_mutex); + + gettimeofday(&tv, 0); + return (tv.tv_sec - tv0.tv_sec) * 1000 + (tv.tv_usec - tv0.tv_usec) / 1000; +} + +static void *thread_func(void *args) +{ + struct dtx_thread_pool *tpool = args; + + pthread_mutex_lock(&tpool->workq_mutex); + while(!tpool->should_quit) { + pthread_cond_wait(&tpool->workq_condvar, &tpool->workq_mutex); + + while(!tpool->should_quit && tpool->workq) { + /* grab the first job */ + struct work_item *job = tpool->workq; + tpool->workq = tpool->workq->next; + if(!tpool->workq) + tpool->workq_tail = 0; + ++tpool->nactive; + --tpool->qsize; + pthread_mutex_unlock(&tpool->workq_mutex); + + /* do the job */ + job->work(job->data); + if(job->done) { + job->done(job->data); + } + + pthread_mutex_lock(&tpool->workq_mutex); + /* notify everyone interested that we're done with this job */ + pthread_cond_broadcast(&tpool->done_condvar); + --tpool->nactive; + } + } + pthread_mutex_unlock(&tpool->workq_mutex); + + return 0; +} +#endif /* USE_THREADS */ + +/* The following highly platform-specific code detects the number + * of processors available in the system. It's used by the thread pool + * to autodetect how many threads to spawn. + * Currently works on: Linux, BSD, Darwin, and Windows. + */ + +#if defined(__APPLE__) && defined(__MACH__) +# ifndef __unix__ +# define __unix__ 1 +# endif /* unix */ +# ifndef __bsd__ +# define __bsd__ 1 +# endif /* bsd */ +#endif /* apple */ + +#if defined(unix) || defined(__unix__) +#include + +# ifdef __bsd__ +# include +# endif +#endif + +#if defined(WIN32) || defined(__WIN32__) +#include +#endif + + +int dtx_tpool_num_processors(void) +{ +#if defined(unix) || defined(__unix__) +# if defined(__bsd__) + /* BSD systems provide the num.processors through sysctl */ + int num, mib[] = {CTL_HW, HW_NCPU}; + size_t len = sizeof num; + + sysctl(mib, 2, &num, &len, 0, 0); + return num; + +# elif defined(__sgi) + /* SGI IRIX flavour of the _SC_NPROC_ONLN sysconf */ + return sysconf(_SC_NPROC_ONLN); +# else + /* Linux (and others?) have the _SC_NPROCESSORS_ONLN sysconf */ + return sysconf(_SC_NPROCESSORS_ONLN); +# endif /* bsd/sgi/other */ + +#elif defined(WIN32) || defined(__WIN32__) + /* under windows we need to call GetSystemInfo */ + SYSTEM_INFO info; + GetSystemInfo(&info); + return info.dwNumberOfProcessors; +#endif +} diff --git a/libs/drawtext/src/tpool.h b/libs/drawtext/src/tpool.h new file mode 100644 index 0000000..5cd73cc --- /dev/null +++ b/libs/drawtext/src/tpool.h @@ -0,0 +1,61 @@ +/* worker thread pool based on POSIX threads + * author: John Tsiombikas + * This code is public domain. + */ +#ifndef THREADPOOL_H_ +#define THREADPOOL_H_ + +struct dtx_thread_pool; + +/* type of the function accepted as work or completion callback */ +typedef void (*dtx_tpool_callback)(void*); + +#ifdef __cplusplus +extern "C" { +#endif + +/* if num_threads == 0, auto-detect how many threads to spawn */ +struct dtx_thread_pool *dtx_tpool_create(int num_threads); +void dtx_tpool_destroy(struct dtx_thread_pool *tpool); + +/* if begin_batch is called before an enqueue, the worker threads will not be + * signalled to start working until end_batch is called. + */ +void dtx_tpool_begin_batch(struct dtx_thread_pool *tpool); +void dtx_tpool_end_batch(struct dtx_thread_pool *tpool); + +/* if enqueue is called without calling begin_batch first, it will immediately + * wake up the worker threads to start working on the enqueued item + */ +int dtx_tpool_enqueue(struct dtx_thread_pool *tpool, void *data, + dtx_tpool_callback work_func, dtx_tpool_callback done_func); +/* clear the work queue. does not cancel any currently running jobs */ +void dtx_tpool_clear(struct dtx_thread_pool *tpool); + +/* returns the number of queued work items */ +int dtx_tpool_queued_jobs(struct dtx_thread_pool *tpool); +/* returns the number of active (working) threads */ +int dtx_tpool_active_jobs(struct dtx_thread_pool *tpool); +/* returns the number of pending jobs, both in queue and active */ +int dtx_tpool_pending_jobs(struct dtx_thread_pool *tpool); + +/* wait for all pending jobs to be completed */ +void dtx_tpool_wait(struct dtx_thread_pool *tpool); +/* wait until the pending jobs are down to the target specified + * for example, to wait until a single job has been completed: + * dtx_tpool_wait_pending(tpool, dtx_tpool_pending_jobs(tpool) - 1); + * this interface is slightly awkward to avoid race conditions. */ +void dtx_tpool_wait_pending(struct dtx_thread_pool *tpool, int pending_target); +/* wait for all pending jobs to be completed for up to "timeout" milliseconds */ +long dtx_tpool_timedwait(struct dtx_thread_pool *tpool, long timeout); + +/* returns the number of processors on the system. + * individual cores in multi-core processors are counted as processors. + */ +int dtx_tpool_num_processors(void); + +#ifdef __cplusplus +} +#endif + +#endif /* THREADPOOL_H_ */ diff --git a/libs/drawtext/src/utf8.c b/libs/drawtext/src/utf8.c new file mode 100644 index 0000000..3ce1b56 --- /dev/null +++ b/libs/drawtext/src/utf8.c @@ -0,0 +1,174 @@ +/* +libdrawtext - a simple library for fast text rendering in OpenGL +Copyright (C) 2011 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see . +*/ +#include "drawtext.h" + +#define U8_IS_FIRST(x) (((((x) >> 7) & 1) == 0) || ((((x) >> 6) & 3) == 3)) + +static const char first_mask[] = { + 0, + 0x7f, /* single byte, 7 bits valid */ + 0x1f, /* two-bytes, 5 bits valid */ + 0xf, /* three-bytes, 4 bits valid */ + 0x7 /* four-bytes, 3 bits valid */ +}; +static const char first_shift[] = { 0, 7, 5, 4, 3 }; /* see above */ + +#define CONT_PREFIX 0x80 +#define CONT_MASK 0x3f +#define CONT_SHIFT 6 + +/* last charcodes for 1, 2, 3 or 4-byte utf8 chars */ +static const int utf8_lastcode[] = { 0x7f, 0x7ff, 0xfff, 0x1fffff }; + +#define prefix_mask(x) (~first_mask[x]) +#define prefix(x) ((prefix_mask(x) << 1) & 0xff) + + +char *dtx_utf8_next_char(char *str) +{ + return str + dtx_utf8_nbytes(str); +} + +char *dtx_utf8_prev_char(char *ptr, char *first) +{ + do { + --ptr; + } while(!U8_IS_FIRST(*ptr) && ptr > first); + return ptr; +} + +int dtx_utf8_char_code(const char *str) +{ + int i, nbytes, shift, code = 0; + int mask; + + if(!U8_IS_FIRST(*str)) { + return -1; + } + + nbytes = dtx_utf8_nbytes(str); + mask = first_mask[nbytes]; + shift = 0; + + for(i=0; i> (7 - i)) & 1) == 0) { + break; + } + numset++; + } + + if(!numset) { + return 1; + } + return numset; +} + +int dtx_utf8_char_count(const char *str) +{ + int n = 0; + + while(*str) { + ++n; + str = dtx_utf8_next_char((char*)str); + } + return n; +} + +int dtx_utf8_char_count_range(const char *str, int nbytes) +{ + int n = 0; + while(*str && nbytes > 0) { + char *next = dtx_utf8_next_char((char*)str); + ++n; + nbytes -= next - str; + str = next; + } + return (nbytes < 0 && n > 0) ? n - 1 : n; +} + +size_t dtx_utf8_from_char_code(int code, char *buf) +{ + size_t nbytes = 0; + int i; + + for(i=0; i<4; i++) { + if(code <= utf8_lastcode[i]) { + nbytes = i + 1; + break; + } + } + + if(!nbytes && buf) { + for(i=0; i<(int)nbytes; i++) { + int idx = nbytes - i - 1; + int mask, shift, prefix; + + if(idx > 0) { + mask = CONT_MASK; + shift = CONT_SHIFT; + prefix = CONT_PREFIX; + } else { + mask = first_mask[nbytes]; + shift = first_shift[nbytes]; + prefix = prefix(nbytes); + } + + buf[idx] = (code & mask) | (prefix & ~mask); + code >>= shift; + } + } + return nbytes; +} + +size_t dtx_utf8_from_string(const wchar_t *str, char *buf) +{ + size_t nbytes = 0; + char *ptr = buf; + + while(*str) { + int cbytes = dtx_utf8_from_char_code(*str++, ptr); + if(ptr) { + ptr += cbytes; + } + nbytes += cbytes; + } + return nbytes; +} diff --git a/sdr/oldfig.p.glsl b/sdr/oldfig.p.glsl deleted file mode 100644 index a51e52c..0000000 --- a/sdr/oldfig.p.glsl +++ /dev/null @@ -1,30 +0,0 @@ -uniform sampler2D tex; - -vec3 rgb2hsv(in vec3 rgb); -vec3 hsv2rgb(in vec3 hsv); - -void main() -{ - vec3 texel = texture2D(tex, gl_TexCoord[0].st).rgb; - vec3 hsv = rgb2hsv(texel); - vec3 rgb = hsv2rgb(hsv * vec3(0.97, 0.8, 0.9)); - - gl_FragColor = vec4(rgb, 1.0); -} - - -vec3 rgb2hsv(in vec3 rgb) -{ - vec4 k = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); - vec4 p = mix(vec4(rgb.bg, k.wz), vec4(rgb.gb, k.xy), step(rgb.b, rgb.g)); - vec4 q = mix(vec4(p.xyw, rgb.r), vec4(rgb.r, p.yzx), step(p.x, rgb.r)); - float d = q.x - min(q.w, q.y); - float e = 1.0e-10; - return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); -} - -vec3 hsv2rgb(in vec3 hsv) -{ - vec3 rgb = clamp(abs(mod(hsv.x * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0 ); - return hsv.z * mix(vec3(1.0), rgb, hsv.y); -} diff --git a/sdr/post.v.glsl b/sdr/post.v.glsl index 4c3c8ef..3566d5f 100644 --- a/sdr/post.v.glsl +++ b/sdr/post.v.glsl @@ -1,5 +1,6 @@ void main() { gl_Position = gl_Vertex; - gl_TexCoord[0] = gl_MultiTexCoord0 * vec4(1.0, -1.0, 1.0, 1.0); + gl_TexCoord[0] = gl_MultiTexCoord0; + gl_FrontColor = gl_Color; } diff --git a/sdr/vignette.p.glsl b/sdr/vignette.p.glsl new file mode 100644 index 0000000..ca28cb9 --- /dev/null +++ b/sdr/vignette.p.glsl @@ -0,0 +1,11 @@ +uniform vec3 color; +uniform float offset, sharpness; + +void main() +{ + vec2 p = gl_TexCoord[0].st * 2.0 - 1.0; + float d = max(length(p) - offset, 0.0); + float vnt = pow(d, sharpness); + + gl_FragColor = vec4(color, gl_Color.a * vnt); +} diff --git a/src/demo.c b/src/demo.c index a4bb0c7..f199939 100644 --- a/src/demo.c +++ b/src/demo.c @@ -5,6 +5,9 @@ #include "post.h" #include "sdr.h" #include "opt.h" +#include "drawtext.h" +#include "imtk.h" +#include "osd.h" void reg_whitted(void); @@ -12,6 +15,9 @@ int win_width, win_height; float win_aspect; long time_msec; +struct dtx_font *fnt_ui; +int fnt_ui_size; + static int reshape_pending; static unsigned int sdr_gamma; @@ -32,6 +38,19 @@ int demo_init(void) glEnable(GL_MULTISAMPLE); } + if(opt.vsync) { + gl_swap_interval(1); + } else { + gl_swap_interval(0); + } + + if(!(fnt_ui = dtx_open_font_glyphmap("data/ui.glyphmap"))) { + fprintf(stderr, "failed to open ui font\n"); + return -1; + } + fnt_ui_size = dtx_get_glyphmap_ptsize(dtx_get_glyphmap(fnt_ui, 0)); + dtx_prepare_range(fnt_ui, fnt_ui_size, 32, 127); + post_init(); if(!(sdr_gamma = create_program_load("sdr/post.v.glsl", "sdr/gamma.p.glsl"))) { @@ -63,6 +82,8 @@ void demo_cleanup(void) for(i=0; idestroy(); } + + dtx_close_font(fnt_ui); } void demo_display(void) @@ -109,6 +130,24 @@ void demo_display(void) glUseProgram(sdr_gamma); overlay_tex(post_fbtex, 1.0); } + + { + static long frames, prev_upd, fps; + long dt; + + frames++; + + dt = time_msec - prev_upd; + if(dt >= 750) { + fps = (frames * 1000 << 8 + 128) / dt; + frames = 0; + prev_upd = time_msec; + } + + print_text(win_width - 80, 20, 1.0, 0.8, 0.1, "fps: %ld.%ld", fps >> 8, ((fps & 0xff) * 10) >> 8); + } + + draw_osd(); } void demo_reshape(int x, int y) @@ -124,6 +163,8 @@ void demo_reshape(int x, int y) glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(50.0, win_aspect, 0.5, 500.0); + + imtk_set_viewport(x, y); } void demo_keyboard(int key, int st) diff --git a/src/demo.h b/src/demo.h index 6ede328..a54f154 100644 --- a/src/demo.h +++ b/src/demo.h @@ -5,6 +5,9 @@ extern int win_width, win_height; extern float win_aspect; extern long time_msec; +extern struct dtx_font *fnt_ui; +extern int fnt_ui_size; + int demo_init(void); void demo_cleanup(void); diff --git a/src/imtk/button.c b/src/imtk/button.c new file mode 100644 index 0000000..f501a50 --- /dev/null +++ b/src/imtk/button.c @@ -0,0 +1,72 @@ +#include +#include +#include "imtk.h" +#include "state.h" +#include "draw.h" + +static void calc_button_size(const char *label, int *wret, int *hret); +static void draw_button(int id, const char *label, int x, int y, int over); + +int imtk_button(int id, const char *label, int x, int y) +{ + int w, h, res = 0; + int over = 0; + + assert(id >= 0); + + if(x == IMTK_AUTO || y == IMTK_AUTO) { + imtk_layout_get_pos(&x, &y); + } + + calc_button_size(label, &w, &h); + + if(imtk_hit_test(x, y, w, h)) { + imtk_set_hot(id); + over = 1; + } + + if(imtk_button_state(IMTK_LEFT_BUTTON)) { + if(over) { + imtk_set_active(id); + } + } else { /* mouse button up */ + if(imtk_is_active(id)) { + imtk_set_active(-1); + if(imtk_is_hot(id) && over) { + res = 1; + } + } + } + + draw_button(id, label, x, y, over); + imtk_layout_advance(w, h); + return res; +} + +static void draw_button(int id, const char *label, int x, int y, int over) +{ + float tcol[4], bcol[4]; + int width, height, active = imtk_is_active(id); + unsigned int attr = 0; + + if(over) attr |= IMTK_FOCUS_BIT; + if(active) attr |= IMTK_PRESS_BIT; + + calc_button_size(label, &width, &height); + + memcpy(tcol, imtk_get_color(IMTK_TOP_COLOR | attr), sizeof tcol); + memcpy(bcol, imtk_get_color(IMTK_BOTTOM_COLOR | attr), sizeof bcol); + + imtk_draw_rect(x, y, width, height, tcol, bcol); + imtk_draw_frame(x, y, width, height, active ? FRAME_INSET : FRAME_OUTSET); + + glColor4fv(imtk_get_color(IMTK_TEXT_COLOR)); + imtk_draw_string(x + 20, y + 15, label); +} + +static void calc_button_size(const char *label, int *wret, int *hret) +{ + int strsz = imtk_string_size(label); + if(wret) *wret = strsz + 40; + if(hret) *hret = 20; +} diff --git a/src/imtk/checkbox.c b/src/imtk/checkbox.c new file mode 100644 index 0000000..6efd0a3 --- /dev/null +++ b/src/imtk/checkbox.c @@ -0,0 +1,109 @@ +#include +#include +#include "imtk.h" +#include "state.h" +#include "draw.h" + + +#define CHECKBOX_SIZE 14 + + +static void draw_checkbox(int id, const char *label, int x, int y, int state, int over); + +int imtk_checkbox(int id, const char *label, int x, int y, int state) +{ + int sz = CHECKBOX_SIZE; + int full_size, over = 0; + + assert(id >= 0); + + if(x == IMTK_AUTO || y == IMTK_AUTO) { + imtk_layout_get_pos(&x, &y); + } + + full_size = sz + imtk_string_size(label) + 5; + if(imtk_hit_test(x, y, full_size, sz)) { + imtk_set_hot(id); + over = 1; + } + + if(imtk_button_state(IMTK_LEFT_BUTTON)) { + if(over) { + imtk_set_active(id); + } + } else { /* mouse button up */ + if(imtk_is_active(id)) { + imtk_set_active(-1); + if(imtk_is_hot(id) && over) { + state = !state; + } + } + } + + draw_checkbox(id, label, x, y, state, over); + imtk_layout_advance(full_size, sz); + return state; +} + +static float v[][2] = { + {-0.2, 0.63}, /* 0 */ + {0.121, 0.325}, /* 1 */ + {0.15, 0.5}, /* 2 */ + {0.28, 0.125}, /* 3 */ + {0.38, 0.33}, /* 4 */ + {0.42, -0.122}, /* 5 */ + {0.58, 0.25}, /* 6 */ + {0.72, 0.52}, /* 7 */ + {0.625, 0.65}, /* 8 */ + {0.89, 0.78}, /* 9 */ + {0.875, 0.92}, /* 10 */ + {1.13, 1.145} /* 11 */ +}; +#define TRI(a, b, c) (glVertex2fv(v[a]), glVertex2fv(v[b]), glVertex2fv(v[c])) + +static void draw_checkbox(int id, const char *label, int x, int y, int state, int over) +{ + static const int sz = CHECKBOX_SIZE; + unsigned int attr = 0; + float tcol[4], bcol[4]; + + if(over) { + attr |= IMTK_FOCUS_BIT; + } + if(imtk_is_active(id)) { + attr |= IMTK_PRESS_BIT; + } + memcpy(tcol, imtk_get_color(IMTK_BOTTOM_COLOR | attr), sizeof tcol); + memcpy(bcol, imtk_get_color(IMTK_TOP_COLOR | attr), sizeof bcol); + + imtk_draw_rect(x, y, sz, sz, tcol, bcol); + imtk_draw_frame(x, y, sz, sz, FRAME_INSET); + + glColor4fv(imtk_get_color(IMTK_TEXT_COLOR)); + if(state) { + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glTranslatef(x, y + sz, 0); + glScalef(sz * 1.2, -sz * 1.3, 1); + + glBegin(GL_TRIANGLES); + glColor4fv(imtk_get_color(IMTK_CHECK_COLOR)); + TRI(0, 1, 2); + TRI(1, 3, 2); + TRI(3, 4, 2); + TRI(3, 5, 4); + TRI(4, 5, 6); + TRI(4, 6, 7); + TRI(4, 7, 8); + TRI(8, 7, 9); + TRI(8, 9, 10); + TRI(10, 9, 11); + glEnd(); + + glPopMatrix(); + } + + glColor4fv(imtk_get_color(IMTK_TEXT_COLOR)); + imtk_draw_string(x + sz + 5, y + sz - 2, label); +} + diff --git a/src/imtk/draw.c b/src/imtk/draw.c new file mode 100644 index 0000000..e1c6fbf --- /dev/null +++ b/src/imtk/draw.c @@ -0,0 +1,270 @@ +#include +#include +#include +#include "draw.h" +#include "imtk.h" + +#define COLOR_MASK 0xff + +/* default colors, can be changed with imtk_set_color */ +static float colors[][4] = { + {0.0, 0.0, 0.0, 1.0}, /* text color */ + {0.75, 0.75, 0.75, 1.0}, /* top color */ + {0.56, 0.56, 0.56, 1.0}, /* bot color */ + {0.9, 0.9, 0.9, 1.0}, /* lit bevel */ + {0.3, 0.3, 0.3, 1.0}, /* shadowed bevel */ + {0.8, 0.25, 0.18, 1.0}, /* cursor color */ + {0.68, 0.85, 1.3, 1.0}, /* selection color */ + {0.75, 0.1, 0.095, 1.0} /* check color */ +}; + +static float focus_factor = 1.15; +static float press_factor = 0.8; +static float alpha = 1.0; +static float bevel = 1.0; + +void imtk_set_color(unsigned int col, float r, float g, float b, float a) +{ + int idx = col & COLOR_MASK; + assert(idx >= 0 && idx < sizeof colors / sizeof *colors); + + colors[idx][0] = r; + colors[idx][1] = g; + colors[idx][2] = b; + colors[idx][3] = a; +} + +float *imtk_get_color(unsigned int col) +{ + static float ret[4]; + int idx = col & COLOR_MASK; + + memcpy(ret, colors + idx, sizeof ret); + if(col & IMTK_FOCUS_BIT) { + ret[0] *= focus_factor; + ret[1] *= focus_factor; + ret[2] *= focus_factor; + } + if(col & IMTK_PRESS_BIT) { + ret[0] *= press_factor; + ret[1] *= press_factor; + ret[2] *= press_factor; + } + if(col & IMTK_SEL_BIT) { + ret[0] *= colors[IMTK_SELECTION_COLOR][0]; + ret[1] *= colors[IMTK_SELECTION_COLOR][1]; + ret[2] *= colors[IMTK_SELECTION_COLOR][2]; + } + ret[3] *= alpha; + return ret; +} + +void imtk_set_alpha(float a) +{ + alpha = a; +} + +float imtk_get_alpha(void) +{ + return alpha; +} + +void imtk_set_bevel_width(float b) +{ + bevel = b; +} + +float imtk_get_bevel_width(void) +{ + return bevel; +} + +void imtk_set_focus_factor(float fact) +{ + focus_factor = fact; +} + +float imtk_get_focus_factor(void) +{ + return focus_factor; +} + +void imtk_set_press_factor(float fact) +{ + press_factor = fact; +} + +float imtk_get_press_factor(void) +{ + return press_factor; +} + +void imtk_draw_rect(int x, int y, int w, int h, float *ctop, float *cbot) +{ + glBegin(GL_QUADS); + if(ctop) { + glColor4fv(ctop); + } + glVertex2f(x, y); + glVertex2f(x + w, y); + + if(cbot) { + glColor4fv(cbot); + } + glVertex2f(x + w, y + h); + glVertex2f(x, y + h); + glEnd(); +} + +void imtk_draw_frame(int x, int y, int w, int h, int style) +{ + float tcol[4], bcol[4]; + float b = imtk_get_bevel_width(); + + if(!b) { + return; + } + + x -= b; + y -= b; + w += b * 2; + h += b * 2; + + switch(style) { + case FRAME_INSET: + memcpy(tcol, imtk_get_color(IMTK_BEVEL_SHAD_COLOR), sizeof tcol); + memcpy(bcol, imtk_get_color(IMTK_BEVEL_LIT_COLOR), sizeof bcol); + break; + + case FRAME_OUTSET: + default: + memcpy(tcol, imtk_get_color(IMTK_BEVEL_LIT_COLOR), sizeof tcol); + memcpy(bcol, imtk_get_color(IMTK_BEVEL_SHAD_COLOR), sizeof bcol); + } + + glBegin(GL_QUADS); + glColor4fv(tcol); + glVertex2f(x, y); + glVertex2f(x + b, y + b); + glVertex2f(x + w - b, y + b); + glVertex2f(x + w, y); + + glVertex2f(x + b, y + b); + glVertex2f(x, y); + glVertex2f(x, y + h); + glVertex2f(x + b, y + h - b); + + glColor4fv(bcol); + glVertex2f(x + b, y + h - b); + glVertex2f(x + w - b, y + h - b); + glVertex2f(x + w, y + h); + glVertex2f(x, y + h); + + glVertex2f(x + w - b, y + b); + glVertex2f(x + w, y); + glVertex2f(x + w, y + h); + glVertex2f(x + w - b, y + h - b); + glEnd(); +} + +void imtk_draw_disc(int x, int y, float rad, int subdiv, float *ctop, float *cbot) +{ + int i; + float t, dtheta, theta = 0.0; + float color[4]; + float cx = (float)x; + float cy = (float)y; + + subdiv += 3; + dtheta = 2.0 * M_PI / subdiv; + + color[0] = (ctop[0] + cbot[0]) * 0.5; + color[1] = (ctop[1] + cbot[1]) * 0.5; + color[2] = (ctop[2] + cbot[2]) * 0.5; + color[3] = (ctop[3] + cbot[3]) * 0.5; + + glBegin(GL_TRIANGLE_FAN); + glColor4fv(color); + glVertex2f(cx, cy); + + for(i=0; i<=subdiv; i++) { + float vx, vy; + + vx = cos(theta); + vy = sin(theta); + theta += dtheta; + + t = (vy + 1.0) / 2.0; + color[0] = ctop[0] + (cbot[0] - ctop[0]) * t; + color[1] = ctop[1] + (cbot[1] - ctop[1]) * t; + color[2] = ctop[2] + (cbot[2] - ctop[2]) * t; + color[3] = ctop[3] + (cbot[3] - ctop[3]) * t; + + glColor4fv(color); + glVertex2f(cx + vx * rad, cy + vy * rad); + } + glEnd(); +} + +void imtk_draw_disc_frame(int x, int y, float inner, float outer, int subdiv, int style) +{ + int i; + float t, dtheta, theta = 0.0; + float color[4], tcol[4], bcol[4]; + float cx = (float)x; + float cy = (float)y; + + switch(style) { + case FRAME_INSET: + memcpy(tcol, imtk_get_color(IMTK_BEVEL_SHAD_COLOR), sizeof tcol); + memcpy(bcol, imtk_get_color(IMTK_BEVEL_LIT_COLOR), sizeof bcol); + break; + + case FRAME_OUTSET: + default: + memcpy(tcol, imtk_get_color(IMTK_BEVEL_LIT_COLOR), sizeof tcol); + memcpy(bcol, imtk_get_color(IMTK_BEVEL_SHAD_COLOR), sizeof bcol); + } + + subdiv += 3; + dtheta = 2.0 * M_PI / subdiv; + + glBegin(GL_QUAD_STRIP); + + for(i=0; i<=subdiv; i++) { + float vx, vy; + + vx = cos(theta); + vy = sin(theta); + + t = (vy + 1.0) / 2.0; + color[0] = tcol[0] + (bcol[0] - tcol[0]) * t; + color[1] = tcol[1] + (bcol[1] - tcol[1]) * t; + color[2] = tcol[2] + (bcol[2] - tcol[2]) * t; + color[3] = tcol[3] + (bcol[3] - tcol[3]) * t; + + vx = cos(theta - M_PI / 4.0); + vy = sin(theta - M_PI / 4.0); + theta += dtheta; + + glColor4fv(color); + glVertex2f(cx + vx * inner, cy + vy * inner); + glVertex2f(cx + vx * outer, cy + vy * outer); + } + glEnd(); +} + +void imtk_draw_string(int x, int y, const char *str) +{ + /* + glRasterPos2i(x, y); + while(*str) { + glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12, *str++); + } + */ +} + +int imtk_string_size(const char *str) +{ + return 0; +} diff --git a/src/imtk/draw.h b/src/imtk/draw.h new file mode 100644 index 0000000..6dbe912 --- /dev/null +++ b/src/imtk/draw.h @@ -0,0 +1,19 @@ +#ifndef DRAW_H_ +#define DRAW_H_ + +#include "opengl.h" +#include "imtk.h" + +enum { + FRAME_OUTSET, + FRAME_INSET +}; + +void imtk_draw_rect(int x, int y, int w, int h, float *ctop, float *cbot); +void imtk_draw_frame(int x, int y, int w, int h, int style); +void imtk_draw_disc(int x, int y, float rad, int subdiv, float *ctop, float *cbot); +void imtk_draw_disc_frame(int x, int y, float inner, float outer, int subdiv, int style); +void imtk_draw_string(int x, int y, const char *str); +int imtk_string_size(const char *str); + +#endif /* DRAW_H_ */ diff --git a/src/imtk/imtk.c b/src/imtk/imtk.c new file mode 100644 index 0000000..545c4c5 --- /dev/null +++ b/src/imtk/imtk.c @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#include +#include "opengl.h" +#include "imtk.h" +#include "state.h" +#include "draw.h" + +void imtk_post_redisplay(void) +{ + glutPostRedisplay(); +} + +void imtk_begin(void) +{ + int width, height; + + glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT | GL_TRANSFORM_BIT); + + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + glDisable(GL_ALPHA_TEST); + glDisable(GL_TEXTURE_1D); + glDisable(GL_TEXTURE_2D); + glDisable(GL_CULL_FACE); + glDisable(GL_SCISSOR_TEST); + glDisable(GL_LIGHTING); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + + imtk_get_viewport(&width, &height); + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glTranslatef(-1, 1, 0); + glScalef(2.0 / width, -2.0 / height, 1.0); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); +} + +void imtk_end(void) +{ + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + glPopAttrib(); +} + +void imtk_label(const char *str, int x, int y) +{ + if(x == IMTK_AUTO || y == IMTK_AUTO) { + imtk_layout_get_pos(&x, &y); + } + + glColor4fv(imtk_get_color(IMTK_TEXT_COLOR)); + imtk_draw_string(x, y + 14, str); + imtk_layout_advance(imtk_string_size(str), 12); +} diff --git a/src/imtk/imtk.h b/src/imtk/imtk.h new file mode 100644 index 0000000..28ff5d0 --- /dev/null +++ b/src/imtk/imtk.h @@ -0,0 +1,105 @@ +#ifndef IMTK_H_ +#define IMTK_H_ + +#include +#include + +#define IMUID (__LINE__ << 10) +#define IMUID_IDX(i) ((__LINE__ << 10) + ((i) << 1)) + + +/* key/button state enum */ +enum { + IMTK_UP, + IMTK_DOWN +}; + +enum { + IMTK_LEFT_BUTTON, + IMTK_MIDDLE_BUTTON, + IMTK_RIGHT_BUTTON +}; + +enum { + IMTK_TEXT_COLOR, + IMTK_TOP_COLOR, + IMTK_BOTTOM_COLOR, + IMTK_BEVEL_LIT_COLOR, + IMTK_BEVEL_SHAD_COLOR, + IMTK_CURSOR_COLOR, + IMTK_SELECTION_COLOR, + IMTK_CHECK_COLOR +}; + +enum { + IMTK_HORIZONTAL, + IMTK_VERTICAL +}; + +#define IMTK_FOCUS_BIT 0x100 +#define IMTK_PRESS_BIT 0x200 +#define IMTK_SEL_BIT 0x400 + +#define IMTK_AUTO INT_MIN + +#ifdef __cplusplus +extern "C" { +#endif + + +void imtk_inp_key(int key, int state); +void imtk_inp_mouse(int bn, int state); +void imtk_inp_motion(int x, int y); + +void imtk_set_viewport(int x, int y); +void imtk_get_viewport(int *width, int *height); + +void imtk_post_redisplay(void); + +void imtk_begin(void); +void imtk_end(void); + +void imtk_label(const char *str, int x, int y); +int imtk_button(int id, const char *label, int x, int y); +int imtk_checkbox(int id, const char *label, int x, int y, int state); +void imtk_textbox(int id, char *textbuf, size_t buf_sz, int x, int y); +float imtk_slider(int id, float pos, float min, float max, int x, int y); +void imtk_progress(int id, float pos, int x, int y); +int imtk_listbox(int id, const char *list, int sel, int x, int y); +int imtk_radiogroup(int id, const char *list, int sel, int x, int y); + +int imtk_begin_frame(int id, const char *label, int x, int y); +void imtk_end_frame(void); + +/* helper functions to create and destroy item lists for listboxes */ +char *imtk_create_list(const char *first, ...); +void imtk_free_list(char *list); + +/* automatic layout */ +int imtk_layout_push(void); +int imtk_layout_pop(void); +void imtk_layout_start(int x, int y); +void imtk_layout_dir(int dir); +void imtk_layout_spacing(int spacing); +void imtk_layout_advance(int width, int height); +void imtk_layout_newline(void); +void imtk_layout_get_pos(int *x, int *y); +void imtk_layout_get_bounds(int *bbox); + +/* defined in draw.c */ +void imtk_set_color(unsigned int col, float r, float g, float b, float a); +float *imtk_get_color(unsigned int col); +void imtk_set_alpha(float a); +float imtk_get_alpha(void); +void imtk_set_bevel_width(float b); +float imtk_get_bevel_width(void); +void imtk_set_focus_factor(float fact); +float imtk_get_focus_factor(void); +void imtk_set_press_factor(float fact); +float imtk_get_press_factor(void); + +#ifdef __cplusplus +} +#endif + +#endif /* IMTK_H_ */ diff --git a/src/imtk/layout.c b/src/imtk/layout.c new file mode 100644 index 0000000..098bf53 --- /dev/null +++ b/src/imtk/layout.c @@ -0,0 +1,101 @@ +#include +#include +#include "imtk.h" + +struct layout { + int box[4], span[4]; + int spacing; + int dir; +}; + +#define MAX_STACK_DEPTH 4 +static struct layout st[MAX_STACK_DEPTH]; +static int top = 0; + +int imtk_layout_push(void) +{ + int newtop = top + 1; + + assert(newtop < MAX_STACK_DEPTH); + if(newtop >= MAX_STACK_DEPTH) { + return -1; + } + + st[newtop] = st[top++]; + return 0; +} + +int imtk_layout_pop(void) +{ + assert(top > 0); + if(top <= 0) { + return -1; + } + top--; + return 0; +} + +void imtk_layout_start(int x, int y) +{ + st[top].box[0] = st[top].span[0] = x; + st[top].box[1] = st[top].span[1] = y; + st[top].box[2] = st[top].box[3] = st[top].span[2] = st[top].span[3] = 0; +/* st[top].spacing = sp; + st[top].dir = dir;*/ +} + +void imtk_layout_dir(int dir) +{ + st[top].dir = dir; + if(dir == IMTK_VERTICAL) { + imtk_layout_newline(); + } +} + +void imtk_layout_spacing(int spacing) +{ + st[top].spacing = spacing; +} + +void imtk_layout_advance(int width, int height) +{ + int max_span_y, max_box_y; + + st[top].span[2] += width + st[top].spacing; + + if(height > st[top].span[3]) { + st[top].span[3] = height; + } + + max_span_y = st[top].span[1] + st[top].span[3]; + max_box_y = st[top].box[1] + st[top].box[3]; + + if(max_span_y > max_box_y) { + st[top].box[3] = max_span_y - st[top].box[1]; + } + if(st[top].span[2] > st[top].box[2]) { + st[top].box[2] = st[top].span[2]; + } + + if(st[top].dir == IMTK_VERTICAL) { + imtk_layout_newline(); + } +} + +void imtk_layout_newline(void) +{ + st[top].span[0] = st[top].box[0]; + st[top].span[1] = st[top].box[1] + st[top].box[3] + st[top].spacing; + st[top].span[2] = st[top].span[3] = 0; +} + +void imtk_layout_get_pos(int *x, int *y) +{ + *x = st[top].span[0] + st[top].span[2]; + *y = st[top].span[1]; +} + +void imtk_layout_get_bounds(int *bbox) +{ + memcpy(bbox, st[top].box, sizeof st[top].box); +} diff --git a/src/imtk/listbox.c b/src/imtk/listbox.c new file mode 100644 index 0000000..74dd7aa --- /dev/null +++ b/src/imtk/listbox.c @@ -0,0 +1,181 @@ +#include +#include +#include +#include "imtk.h" +#include "state.h" +#include "draw.h" + +#define ITEM_HEIGHT 18 +#define PAD 3 + +static int list_radio(int id, const char *list, int sel, int x, int y, void (*draw)()); +static void draw_listbox(int id, const char *list, int sel, int x, int y, int width, int nitems, int over); +static void draw_radio(int id, const char *list, int sel, int x, int y, int width, int nitems, int over); + +int imtk_listbox(int id, const char *list, int sel, int x, int y) +{ + return list_radio(id, list, sel, x, y, draw_listbox); +} + +int imtk_radiogroup(int id, const char *list, int sel, int x, int y) +{ + return list_radio(id, list, sel, x, y, draw_radio); +} + +static int list_radio(int id, const char *list, int sel, int x, int y, void (*draw)()) +{ + int i, max_width, nitems, over; + const char *ptr; + + assert(id >= 0); + + if(x == IMTK_AUTO || y == IMTK_AUTO) { + imtk_layout_get_pos(&x, &y); + } + + max_width = 0; + over = 0; + + ptr = list; + for(i=0; *ptr; i++) { + int strsz = imtk_string_size(ptr) + 2 * PAD; + if(strsz > max_width) { + max_width = strsz; + } + ptr += strlen(ptr) + 1; + + if(imtk_hit_test(x, y + i * ITEM_HEIGHT, max_width, ITEM_HEIGHT)) { + imtk_set_hot(id); + over = i + 1; + } + } + nitems = i; + + if(imtk_button_state(IMTK_LEFT_BUTTON)) { + if(over) { + imtk_set_active(id); + } + } else { + if(imtk_is_active(id)) { + imtk_set_active(-1); + if(imtk_is_hot(id) && over) { + sel = over - 1; + } + } + } + + draw(id, list, sel, x, y, max_width, nitems, over); + imtk_layout_advance(max_width, ITEM_HEIGHT * nitems); + return sel; +} + +char *imtk_create_list(const char *first, ...) +{ + int sz; + char *buf, *item; + va_list ap; + + if(!first) { + return 0; + } + + sz = strlen(first) + 2; + if(!(buf = malloc(sz))) { + return 0; + } + memcpy(buf, first, sz - 2); + buf[sz - 1] = buf[sz - 2] = 0; + + va_start(ap, first); + while((item = va_arg(ap, char*))) { + int len = strlen(item); + char *tmp = realloc(buf, sz + len + 1); + if(!tmp) { + free(buf); + return 0; + } + buf = tmp; + + memcpy(buf + sz - 1, item, len); + sz += len + 1; + buf[sz - 1] = buf[sz - 2] = 0; + } + va_end(ap); + + return buf; +} + +void imtk_free_list(char *list) +{ + free(list); +} + +static void draw_listbox(int id, const char *list, int sel, int x, int y, int width, int nitems, int over) +{ + int i; + const char *item = list; + + glColor4fv(imtk_get_color(IMTK_TEXT_COLOR)); + + for(i=0; i +#include "imtk.h" +#include "draw.h" + +#define PROGR_SIZE 100 +#define PROGR_HEIGHT 15 + +static void draw_progress(int id, float pos, int x, int y); + +void imtk_progress(int id, float pos, int x, int y) +{ + if(x == IMTK_AUTO || y == IMTK_AUTO) { + imtk_layout_get_pos(&x, &y); + } + + draw_progress(id, pos, x, y); + imtk_layout_advance(PROGR_SIZE, PROGR_HEIGHT); +} + +static void draw_progress(int id, float pos, int x, int y) +{ + int bar_size = PROGR_SIZE * pos; + float b = imtk_get_bevel_width(); + float tcol[4], bcol[4]; + + if(pos < 0.0) pos = 0.0; + if(pos > 1.0) pos = 1.0; + + memcpy(tcol, imtk_get_color(IMTK_BOTTOM_COLOR), sizeof tcol); + memcpy(bcol, imtk_get_color(IMTK_TOP_COLOR), sizeof bcol); + + /* trough */ + imtk_draw_rect(x - b, y - b, PROGR_SIZE + b * 2, PROGR_HEIGHT + b * 2, tcol, bcol); + imtk_draw_frame(x - b, y - b, PROGR_SIZE + b * 2, PROGR_HEIGHT + b * 2, FRAME_INSET); + + /* bar */ + if(pos > 0.0) { + memcpy(tcol, imtk_get_color(IMTK_TOP_COLOR), sizeof tcol); + memcpy(bcol, imtk_get_color(IMTK_BOTTOM_COLOR), sizeof bcol); + + imtk_draw_rect(x, y, bar_size, PROGR_HEIGHT, tcol, bcol); + imtk_draw_frame(x, y, bar_size, PROGR_HEIGHT, FRAME_OUTSET); + } +} diff --git a/src/imtk/slider.c b/src/imtk/slider.c new file mode 100644 index 0000000..6c50d03 --- /dev/null +++ b/src/imtk/slider.c @@ -0,0 +1,110 @@ +#include +#include +#include +#include "imtk.h" +#include "state.h" +#include "draw.h" + +#define SLIDER_SIZE 100 +#define THUMB_WIDTH 10 +#define THUMB_HEIGHT 20 + +static int draw_slider(int id, float pos, float min, float max, int x, int y, int over); + +float imtk_slider(int id, float pos, float min, float max, int x, int y) +{ + int mousex, mousey, thumb_x, thumb_y, txsz, over = 0; + float range = max - min; + + assert(id >= 0); + + if(x == IMTK_AUTO || y == IMTK_AUTO) { + imtk_layout_get_pos(&x, &y); + } + y += THUMB_HEIGHT / 2; + + imtk_get_mouse(&mousex, &mousey); + + pos = (pos - min) / range; + if(pos < 0.0) pos = 0.0; + if(pos > 1.0) pos = 1.0; + + thumb_x = x + SLIDER_SIZE * pos - THUMB_WIDTH / 2; + thumb_y = y - THUMB_HEIGHT / 2; + + if(imtk_hit_test(thumb_x, thumb_y, THUMB_WIDTH, THUMB_HEIGHT)) { + imtk_set_hot(id); + over = 1; + } + + if(imtk_button_state(IMTK_LEFT_BUTTON)) { + if(over && imtk_is_hot(id)) { + if(!imtk_is_active(id)) { + imtk_set_prev_mouse(mousex, mousey); + } + imtk_set_active(id); + } + } else { + if(imtk_is_active(id)) { + imtk_set_active(-1); + } + } + + if(imtk_is_active(id)) { + int prevx; + float dx; + + imtk_get_prev_mouse(&prevx, 0); + + dx = (float)(mousex - prevx) / (float)SLIDER_SIZE; + pos += dx; + imtk_set_prev_mouse(mousex, mousey); + + if(pos < 0.0) pos = 0.0; + if(pos > 1.0) pos = 1.0; + } + + txsz = draw_slider(id, pos, min, max, x, y, over); + imtk_layout_advance(SLIDER_SIZE + THUMB_WIDTH + txsz, THUMB_HEIGHT); + return pos * range + min; +} + + +static int draw_slider(int id, float pos, float min, float max, int x, int y, int over) +{ + float range = max - min; + int thumb_x, thumb_y; + char buf[32]; + float tcol[4], bcol[4]; + unsigned int attr = 0; + + thumb_x = x + SLIDER_SIZE * pos - THUMB_WIDTH / 2; + thumb_y = y - THUMB_HEIGHT / 2; + + memcpy(tcol, imtk_get_color(IMTK_BOTTOM_COLOR), sizeof tcol); + memcpy(bcol, imtk_get_color(IMTK_TOP_COLOR), sizeof bcol); + + /* draw trough */ + imtk_draw_rect(x, y - 2, SLIDER_SIZE, 5, tcol, bcol); + imtk_draw_frame(x, y - 2, SLIDER_SIZE, 5, FRAME_INSET); + + if(over) { + attr |= IMTK_FOCUS_BIT; + } + if(imtk_is_active(id)) { + attr |= IMTK_PRESS_BIT; + } + memcpy(tcol, imtk_get_color(IMTK_TOP_COLOR | attr), sizeof tcol); + memcpy(bcol, imtk_get_color(IMTK_BOTTOM_COLOR | attr), sizeof bcol); + + /* draw handle */ + imtk_draw_rect(thumb_x, thumb_y, THUMB_WIDTH, THUMB_HEIGHT, tcol, bcol); + imtk_draw_frame(thumb_x, thumb_y, THUMB_WIDTH, THUMB_HEIGHT, FRAME_OUTSET); + + /* draw display */ + sprintf(buf, "%.3f", pos * range + min); + glColor4fv(imtk_get_color(IMTK_TEXT_COLOR)); + imtk_draw_string(x + SLIDER_SIZE + THUMB_WIDTH / 2 + 2, y + 4, buf); + return imtk_string_size(buf); +} + diff --git a/src/imtk/state.c b/src/imtk/state.c new file mode 100644 index 0000000..f177f4d --- /dev/null +++ b/src/imtk/state.c @@ -0,0 +1,154 @@ +#include "state.h" +#include "imtk.h" + +struct key_node { + int key; + struct key_node *next; +}; + + +struct imtk_state st = { + 1, 1, /* scr_width/scr_height */ + 0, 0, 0, 0, 0, /* mousex/mousey, prevx, prevy, mouse_bnmask */ + -1, -1, -1, -1 /* active, hot, input, prev_active */ +}; + +static struct key_node *key_list, *key_tail; + + + +void imtk_inp_key(int key, int state) +{ + if(state == IMTK_DOWN) { + struct key_node *node; + + if(!(node = malloc(sizeof *node))) { + return; + } + node->key = key; + node->next = 0; + + if(key_list) { + key_tail->next = node; + key_tail = node; + } else { + key_list = key_tail = node; + } + } + + imtk_post_redisplay(); +} + +void imtk_inp_mouse(int bn, int state) +{ + if(state == IMTK_DOWN) { + st.mouse_bnmask |= 1 << bn; + } else { + st.mouse_bnmask &= ~(1 << bn); + } + imtk_post_redisplay(); +} + +void imtk_inp_motion(int x, int y) +{ + st.mousex = x; + st.mousey = y; + + imtk_post_redisplay(); +} + +void imtk_set_viewport(int x, int y) +{ + st.scr_width = x; + st.scr_height = y; +} + +void imtk_get_viewport(int *width, int *height) +{ + if(width) *width = st.scr_width; + if(height) *height = st.scr_height; +} + + +void imtk_set_active(int id) +{ + if(id == -1 || st.hot == id) { + st.prev_active = st.active; + st.active = id; + } +} + +int imtk_is_active(int id) +{ + return st.active == id; +} + +int imtk_set_hot(int id) +{ + if(st.active == -1) { + st.hot = id; + return 1; + } + return 0; +} + +int imtk_is_hot(int id) +{ + return st.hot == id; +} + +void imtk_set_focus(int id) +{ + st.input = id; +} + +int imtk_has_focus(int id) +{ + return st.input == id; +} + +int imtk_hit_test(int x, int y, int w, int h) +{ + return st.mousex >= x && st.mousex < (x + w) && + st.mousey >= y && st.mousey < (y + h); +} + +void imtk_get_mouse(int *xptr, int *yptr) +{ + if(xptr) *xptr = st.mousex; + if(yptr) *yptr = st.mousey; +} + +void imtk_set_prev_mouse(int x, int y) +{ + st.prevx = x; + st.prevy = y; +} + +void imtk_get_prev_mouse(int *xptr, int *yptr) +{ + if(xptr) *xptr = st.prevx; + if(yptr) *yptr = st.prevy; +} + +int imtk_button_state(int bn) +{ + return st.mouse_bnmask & (1 << bn); +} + + +int imtk_get_key(void) +{ + int key = -1; + struct key_node *node = key_list; + + if(node) { + key = node->key; + key_list = node->next; + if(!key_list) { + key_tail = 0; + } + free(node); + } + return key; +} diff --git a/src/imtk/state.h b/src/imtk/state.h new file mode 100644 index 0000000..42933f7 --- /dev/null +++ b/src/imtk/state.h @@ -0,0 +1,26 @@ +#ifndef STATE_H_ +#define STATE_H_ + +struct imtk_state { + int scr_width, scr_height; + int mousex, mousey, prevx, prevy, mouse_bnmask; + int active, hot, input, prev_active; +}; + +void imtk_set_active(int id); +int imtk_is_active(int id); +int imtk_set_hot(int id); +int imtk_is_hot(int id); +void imtk_set_focus(int id); +int imtk_has_focus(int id); +int imtk_hit_test(int x, int y, int w, int h); + +void imtk_get_mouse(int *xptr, int *yptr); +void imtk_set_prev_mouse(int x, int y); +void imtk_get_prev_mouse(int *xptr, int *yptr); +int imtk_button_state(int bn); + +int imtk_get_key(void); + + +#endif /* STATE_H_ */ diff --git a/src/imtk/textbox.c b/src/imtk/textbox.c new file mode 100644 index 0000000..8fda2f8 --- /dev/null +++ b/src/imtk/textbox.c @@ -0,0 +1,154 @@ +#include +#include +#include +#include "imtk.h" +#include "state.h" +#include "draw.h" + +#define TEXTBOX_SIZE 100 +#define TEXTBOX_HEIGHT 20 + +static void draw_textbox(int id, char *text, int cursor, int x, int y, int over); + + +void imtk_textbox(int id, char *textbuf, size_t buf_sz, int x, int y) +{ + int key, len, cursor = 0, over = 0; + + assert(id >= 0); + + if(x == IMTK_AUTO || y == IMTK_AUTO) { + imtk_layout_get_pos(&x, &y); + } + + len = strlen(textbuf); + + /* HACK! using last element of textbuf for saving cursor position */ + if((cursor = textbuf[buf_sz - 1]) > len || cursor < 0) { + cursor = len; + } + + if(imtk_hit_test(x, y, TEXTBOX_SIZE, TEXTBOX_HEIGHT)) { + imtk_set_hot(id); + over = 1; + } + + if(imtk_button_state(IMTK_LEFT_BUTTON)) { + if(over) { + imtk_set_active(id); + } + } else { + if(imtk_is_active(id)) { + imtk_set_active(-1); + if(imtk_is_hot(id) && over) { + imtk_set_focus(id); + } + } + } + + if(imtk_has_focus(id)) { + while((key = imtk_get_key()) != -1) { + if(!(key & 0xff00) && isprint(key)) { + if(len < buf_sz - 1) { + if(cursor == len) { + textbuf[len++] = (char)key; + cursor = len; + } else { + memmove(textbuf + cursor + 1, textbuf + cursor, len - cursor); + len++; + textbuf[cursor++] = (char)key; + } + } + } else { + key &= 0xff; + + switch(key) { + case '\b': + if(cursor > 0) { + if(cursor == len) { + textbuf[--cursor] = 0; + } else { + memmove(textbuf + cursor - 1, textbuf + cursor, len - cursor); + textbuf[--len] = 0; + cursor--; + } + } + break; + + case 127: /* del */ + if(cursor < len) { + memmove(textbuf + cursor, textbuf + cursor + 1, len - cursor); + textbuf[--len] = 0; + } + break; + + case GLUT_KEY_LEFT: + if(cursor > 0) { + cursor--; + } + break; + + case GLUT_KEY_RIGHT: + if(cursor < len) { + cursor++; + } + break; + + case GLUT_KEY_HOME: + cursor = 0; + break; + + case GLUT_KEY_END: + cursor = len; + break; + + default: + break; + } + } + } + } + + textbuf[buf_sz - 1] = cursor; + draw_textbox(id, textbuf, cursor, x, y, over); + imtk_layout_advance(TEXTBOX_SIZE, TEXTBOX_HEIGHT); +} + + +static void draw_textbox(int id, char *text, int cursor, int x, int y, int over) +{ + float tcol[4], bcol[4]; + unsigned int attr = 0; + + if(over) { + attr |= IMTK_FOCUS_BIT; + } + memcpy(tcol, imtk_get_color(IMTK_BOTTOM_COLOR | attr), sizeof tcol); + memcpy(bcol, imtk_get_color(IMTK_TOP_COLOR | attr), sizeof bcol); + + imtk_draw_rect(x, y, TEXTBOX_SIZE, TEXTBOX_HEIGHT, tcol, bcol); + + if(imtk_has_focus(id)) { + int strsz; + char tmp; + + tmp = text[cursor]; + text[cursor] = 0; + strsz = imtk_string_size(text); + text[cursor] = tmp; + + glBegin(GL_QUADS); + glColor4fv(imtk_get_color(IMTK_CURSOR_COLOR)); + glVertex2f(x + strsz + 2, y + 2); + glVertex2f(x + strsz + 4, y + 2); + glVertex2f(x + strsz + 4, y + 18); + glVertex2f(x + strsz + 2, y + 18); + glEnd(); + } + + glColor4fv(imtk_get_color(IMTK_TEXT_COLOR)); + imtk_draw_string(x + 2, y + 15, text); + + imtk_draw_frame(x, y, TEXTBOX_SIZE, TEXTBOX_HEIGHT, FRAME_INSET); +} + diff --git a/src/opengl.c b/src/opengl.c index beba99d..2d5e6a4 100644 --- a/src/opengl.c +++ b/src/opengl.c @@ -1,7 +1,40 @@ #include "opengl.h" +#ifdef __unix__ +#include "glxew.h" + +static Display *dpy; +static Window win; +#endif +#ifdef _WIN32 +#include "wglew.h" +#endif + + int init_opengl(void) { glewInit(); + +#ifdef __unix__ + dpy = glXGetCurrentDisplay(); + win = glXGetCurrentDrawable(); +#endif + return 0; } + +void gl_swap_interval(int val) +{ +#ifdef __unix__ + if(GLX_EXT_swap_control) { + glXSwapIntervalEXT(dpy, win, val); + } else if(GLX_SGI_swap_control) { + glXSwapIntervalSGI(val); + } +#endif +#ifdef _WIN32 + if(WGL_EXT_swap_control) { + wglSwapIntervalEXT(val); + } +#endif +} diff --git a/src/opengl.h b/src/opengl.h index fd4b820..759fd90 100644 --- a/src/opengl.h +++ b/src/opengl.h @@ -6,4 +6,6 @@ int init_opengl(void); +void gl_swap_interval(int val); + #endif /* OPENGL_H_ */ diff --git a/src/opt.c b/src/opt.c index 796722c..01be1a6 100644 --- a/src/opt.c +++ b/src/opt.c @@ -11,7 +11,8 @@ struct options opt = { 1, /* fullscreen */ 1, /* music */ 1, /* sRGB */ - 1 /* anti-aliasing */ + 1, /* anti-aliasing */ + 1 /* vsync */ }; int parse_args(int argc, char **argv) @@ -40,6 +41,10 @@ int parse_args(int argc, char **argv) opt.music = 1; } else if(strcmp(argv[i], "-nomusic") == 0) { opt.music = 0; + } else if(strcmp(argv[i], "-vsync") == 0) { + opt.vsync = 1; + } else if(strcmp(argv[i], "-novsync") == 0) { + opt.vsync = 0; } else if(strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "-help") == 0) { print_usage(argv[0]); exit(0); @@ -61,6 +66,7 @@ static void print_usage(const char *argv0) printf(" -srgb/-nosrgb: enable/disable sRGB framebuffer\n"); printf(" -aa/-noaa: enable/disable multisample anti-aliasing\n"); printf(" -music/-nomusic: enable/disable music playback\n"); + printf(" -vsync/-novsync: enable/disable vertical sync\n"); printf(" -h,-help: print usage and exit\n"); } @@ -77,6 +83,7 @@ int read_cfg(const char *fname) opt.music = ts_lookup_int(ts, "demo.music", opt.music); opt.srgb = ts_lookup_int(ts, "demo.srgb", opt.srgb); opt.msaa = ts_lookup_int(ts, "demo.aa", opt.msaa); + opt.vsync = ts_lookup_int(ts, "demo.vsync", opt.vsync); ts_free_tree(ts); return 0; diff --git a/src/opt.h b/src/opt.h index bad7997..1f88752 100644 --- a/src/opt.h +++ b/src/opt.h @@ -6,6 +6,7 @@ struct options { int fullscr; int music; int srgb, msaa; + int vsync; }; extern struct options opt; diff --git a/src/osd.c b/src/osd.c new file mode 100644 index 0000000..415cb86 --- /dev/null +++ b/src/osd.c @@ -0,0 +1,184 @@ +#include +#include +#include +#include +#include "cgmath/cgmath.h" +#include "opengl.h" +#include "osd.h" +#include "demo.h" + + +struct message { + long start_time, show_until; + char *str; + cgm_vec3 color; + struct message *next; +}; +static struct message *msglist; + +struct text { + char *str; + cgm_vec2 pos; + cgm_vec3 color; + struct text *next; +}; +static struct text *txlist; + +static long timeout = 2000; +static long trans_time = 250; + + +void set_message_timeout(long tm) +{ + timeout = tm; +} + +void osd_printf(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + show_messagev(timeout, 1, 1, 1, fmt, ap); + va_end(ap); +} + +void show_message(long timeout, float r, float g, float b, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + show_messagev(timeout, r, g, b, fmt, ap); + va_end(ap); +} + +void show_messagev(long timeout, float r, float g, float b, const char *fmt, va_list ap) +{ + char buf[512]; + struct message *msg; + struct message dummy; + int len; + + vsnprintf(buf, sizeof buf, fmt, ap); + + if(!(msg = malloc(sizeof *msg))) { + perror("failed to allocate memory"); + abort(); + } + len = strlen(buf); + if(!(msg->str = malloc(len + 1))) { + perror("failed to allocate memory"); + abort(); + } + memcpy(msg->str, buf, len + 1); + msg->start_time = time_msec; + msg->show_until = time_msec + timeout; + msg->color.x = r; + msg->color.y = g; + msg->color.z = b; + + dummy.next = msglist; + struct message *prev = &dummy; + while(prev->next && prev->next->show_until < msg->show_until) { + prev = prev->next; + } + msg->next = prev->next; + prev->next = msg; + msglist = dummy.next; +} + +void print_text(float x, float y, float r, float g, float b, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + print_textv(x, y, r, g, b, fmt, ap); + va_end(ap); +} + +void print_textv(float x, float y, float r, float g, float b, const char *fmt, va_list ap) +{ + char buf[512]; + int len; + struct text *tx; + + vsnprintf(buf, sizeof buf, fmt, ap); + + if(!(tx = malloc(sizeof *tx))) { + perror("failed to allocate memory"); + abort(); + } + len = strlen(buf); + if(!(tx->str = malloc(len + 1))) { + perror("failed to allocate memory"); + abort(); + } + memcpy(tx->str, buf, len + 1); + tx->color.x = r; + tx->color.y = g; + tx->color.z = b; + tx->pos.x = x; + tx->pos.y = -y; + + tx->next = txlist; + txlist = tx; +} + +void draw_osd(void) +{ + if(!fnt_ui) return; + + while(msglist && msglist->show_until <= time_msec) { + struct message *msg = msglist; + msglist = msg->next; + free(msg->str); + free(msg); + } + + dtx_use_font(fnt_ui, fnt_ui_size); + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glOrtho(0, win_width, -win_height, 0, -1, 1); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + glPushAttrib(GL_ENABLE_BIT); + glDisable(GL_LIGHTING); + glDisable(GL_DEPTH_TEST); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glUseProgram(0); + + struct message *msg = msglist; + while(msg) { + long t = time_msec - msg->start_time; + long dur = msg->show_until - msg->start_time; + float alpha = cgm_smoothstep(0, trans_time, t) * + (1.0 - cgm_smoothstep(dur - trans_time, dur, t)); + glColor4f(msg->color.x, msg->color.y, msg->color.z, alpha); + glTranslatef(0, -dtx_line_height(), 0); + dtx_string(msg->str); + msg = msg->next; + } + + while(txlist) { + struct text *tx = txlist; + txlist = txlist->next; + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glTranslatef(tx->pos.x, tx->pos.y, 0); + + glColor3f(tx->color.x, tx->color.y, tx->color.z); + dtx_string(tx->str); + + free(tx->str); + free(tx); + } + + glPopAttrib(); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); +} diff --git a/src/osd.h b/src/osd.h new file mode 100644 index 0000000..ac0e6e4 --- /dev/null +++ b/src/osd.h @@ -0,0 +1,16 @@ +#ifndef OSD_H_ +#define OSD_H_ + +#include + +void set_message_timeout(long timeout); +void osd_printf(const char *fmt, ...); +void show_message(long timeout, float r, float g, float b, const char *fmt, ...); +void show_messagev(long timeout, float r, float g, float b, const char *fmt, va_list ap); + +void print_text(float x, float y, float r, float g, float b, const char *fmt, ...); +void print_textv(float x, float y, float r, float g, float b, const char *fmt, va_list ap); + +void draw_osd(void); + +#endif /* OSD_H_ */ diff --git a/src/part.c b/src/part.c index 8067a1e..15e8a37 100644 --- a/src/part.c +++ b/src/part.c @@ -1,5 +1,6 @@ #include "part.h" #include "demo.h" +#include "osd.h" struct demo_part *cur_part, *prev_part; @@ -15,6 +16,10 @@ void switch_part(struct demo_part *part) { part->start_time = time_msec; + osd_printf("Part: %s", part->name); + + if(part == cur_part) return; + if(cur_part) { prev_part = cur_part; cur_part = part; diff --git a/src/part_whitted.c b/src/part_whitted.c index eb0fe5b..18bed8d 100644 --- a/src/part_whitted.c +++ b/src/part_whitted.c @@ -83,9 +83,6 @@ static void draw(long tm) glRotatef(-cam_phi, 1, 0, 0); glTranslatef(0, 0, cam_dist); - glBindFramebuffer(GL_FRAMEBUFFER, post_fbo[0]); - glClear(GL_COLOR_BUFFER_BIT); - glUseProgram(sdr); glUniform1f(uloc_aspect, win_aspect); @@ -100,13 +97,16 @@ static void draw(long tm) glVertex2f(-1, 1); glEnd(); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glUseProgram(post_sdr[POST_OLDFIG]); - overlay_tex(post_fbtex, 1.0); + vignette(0.43, 0.38, 0.45, 0.8, 1.0); - if(dbgtex) { + if(dbgtex && dbg_alpha > 0.0) { glUseProgram(0); + glMatrixMode(GL_TEXTURE); + glLoadIdentity(); + glScalef(1, -1, 1); overlay_tex(dbgtex, dbg_alpha); + glMatrixMode(GL_TEXTURE); + glLoadIdentity(); } } diff --git a/src/post.c b/src/post.c index 91109b2..c834073 100644 --- a/src/post.c +++ b/src/post.c @@ -12,18 +12,21 @@ unsigned int post_fbo[2]; struct texture post_fbtex[2]; int post_fbtex_cur; -unsigned int post_sdr[MAX_POST_SDR]; +unsigned int sdr_vgn; +int vgn_uloc_color, vgn_uloc_offs, vgn_uloc_sharp; int post_init(void) { int i; - static const char *psdr_fname[] = {"sdr/oldfig.p.glsl"}; - for(i=0; iid, (float)tex->width / tex->height, alpha); + unsigned int tid; + float aspect; + if(tex) { + tid = tex->id; + aspect = (float)tex->width / tex->height; + } else { + tid = 0; + aspect = 1.0f; + } + overlay(tid, aspect, alpha); +} + +void vignette(float r, float g, float b, float offs, float sharp) +{ + glUseProgram(sdr_vgn); + if(vgn_uloc_color) { + glUniform3f(vgn_uloc_color, r, g, b); + } + if(vgn_uloc_offs) { + glUniform1f(vgn_uloc_offs, offs); + } + if(vgn_uloc_sharp) { + glUniform1f(vgn_uloc_sharp, sharp); + } + overlay(0, 1.0, 1.0); } diff --git a/src/post.h b/src/post.h index c9e586e..f228ded 100644 --- a/src/post.h +++ b/src/post.h @@ -3,13 +3,6 @@ #include "texture.h" -enum { - POST_OLDFIG, - - MAX_POST_SDR -}; -extern unsigned int post_sdr[MAX_POST_SDR]; - extern struct texture post_fbtex[2]; extern int post_fbtex_cur; extern unsigned int post_fbo[2]; @@ -21,4 +14,7 @@ void post_reshape(int x, int y); void overlay(unsigned int tex, float aspect, float alpha); void overlay_tex(struct texture *tex, float alpha); +/* blends a vignette overlay onto the framebuffer */ +void vignette(float r, float g, float b, float offs, float sharp); + #endif /* POST_H_ */