├── .gitignore ├── LICENSE ├── README.md ├── doc └── src │ ├── book.xml │ ├── egd_ug.xmlsrc │ ├── img.erl │ ├── img_esi.erl │ ├── img_esi_result.gif │ ├── notes.xml │ ├── part.xml │ ├── ref_man.xml │ ├── test1.gif │ ├── test2.gif │ ├── test3.gif │ └── test4.gif ├── priv └── fonts │ └── 6x11_latin1.wingsfont ├── rebar.config ├── src ├── egd.app.src ├── egd.erl ├── egd.hrl ├── egd_font.erl ├── egd_png.erl ├── egd_primitives.erl └── egd_render.erl └── test ├── egd.spec └── egd_SUITE.erl /.gitignore: -------------------------------------------------------------------------------- 1 | ebin 2 | log 3 | logs 4 | _build 5 | 6 | doc/specs/ 7 | doc/src/egd.xml 8 | doc/src/egd_ug.xml 9 | doc/src/ref_man.xml 10 | 11 | .rebar3 12 | _* 13 | *.o 14 | *.beam 15 | *.plt 16 | *.swp 17 | *.swo 18 | .erlang.cookie 19 | erl_crash.dump 20 | .rebar 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Erlang Graphical Drawer 2 | ===== 3 | 4 | Erlang Graphical Drawer is an interface for 2d-image rendering and is used by Percept to generate dynamic graphs to its web 5 | pages. All code is pure erlang, no drivers needed. 6 | 7 | Build 8 | ----- 9 | 10 | $ rebar3 compile 11 | -------------------------------------------------------------------------------- /doc/src/book.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 2007 8 | 2016 9 | Ericsson AB, All Rights Reserved 10 | 11 | 12 | Licensed under the Apache License, Version 2.0 (the "License"); 13 | you may not use this file except in compliance with the License. 14 | You may obtain a copy of the License at 15 | 16 | http://www.apache.org/licenses/LICENSE-2.0 17 | 18 | Unless required by applicable law or agreed to in writing, software 19 | distributed under the License is distributed on an "AS IS" BASIS, 20 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 | See the License for the specific language governing permissions and 22 | limitations under the License. 23 | 24 | The Initial Developer of the Original Code is Ericsson AB. 25 | 26 | 27 | Percept 28 | Björn-Egil Dahlberg 29 | 30 | 2007-11-02 31 | 0.5.0 32 | book.xml 33 |
34 | 35 | 36 | Erlang Graphics Draw 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
52 | 53 | -------------------------------------------------------------------------------- /doc/src/egd_ug.xmlsrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 2007 8 | 2016 9 | Ericsson AB, All Rights Reserved 10 | 11 | 12 | Licensed under the Apache License, Version 2.0 (the "License"); 13 | you may not use this file except in compliance with the License. 14 | You may obtain a copy of the License at 15 | 16 | http://www.apache.org/licenses/LICENSE-2.0 17 | 18 | Unless required by applicable law or agreed to in writing, software 19 | distributed under the License is distributed on an "AS IS" BASIS, 20 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 | See the License for the specific language governing permissions and 22 | limitations under the License. 23 | 24 | The Initial Developer of the Original Code is Ericsson AB. 25 | 26 | 27 | egd 28 | Björn-Egil Dahlberg 29 | 30 | 2007-11-03 31 | A 32 | egd_ug.xml 33 |
34 |
35 | Introduction 36 |

37 | The egd module is an interface for 2d-image rendering and is used by 38 | Percept to generate dynamic graphs to its web pages. All code is pure 39 | erlang, no drivers needed. 40 |

41 |

42 | The library is intended for small to medium image sizes with low 43 | complexity for optimal performance. The library handles horizontal 44 | lines better then vertical lines. 45 |

46 |

47 | The foremost purpose for this module is to enable users to 48 | generate images from erlang code and/or datasets and to 49 | send these images to either files or web servers. 50 |

51 |
52 |
53 | File example 54 |

Drawing examples:

55 | 56 |

First save.

57 | 58 | test1.png 59 | 60 | 61 |

Second save.

62 | 63 | test2.png 64 | 65 | 66 |

Third save.

67 | 68 | test3.png 69 | 70 | 71 |

Fourth save.

72 | 73 | test4.png 74 | 75 |
76 |
77 | ESI example 78 |

Using egd with inets ESI to generate images on the fly:

79 | 80 | 81 | Example of result. 82 | 83 |

84 | For more information regarding ESI, please see inets application 85 | mod_esi. 86 |

87 |
88 |
89 | 90 | 91 | -------------------------------------------------------------------------------- /doc/src/img.erl: -------------------------------------------------------------------------------- 1 | -module(img). 2 | 3 | -export([do/0]). 4 | 5 | do() -> 6 | Im = egd:create(200,200), 7 | Red = egd:color({255,0,0}), 8 | Green = egd:color({0,255,0}), 9 | Blue = egd:color({0,0,255}), 10 | Black = egd:color({0,0,0}), 11 | Yellow = egd:color({255,255,0}), 12 | 13 | % Line and fillRectangle 14 | 15 | egd:filledRectangle(Im, {20,20}, {180,180}, Red), 16 | egd:line(Im, {0,0}, {200,200}, Black), 17 | 18 | egd:save(egd:render(Im, png), "/home/egil/test1.png"), 19 | 20 | egd:filledEllipse(Im, {45, 60}, {55, 70}, Yellow), 21 | egd:filledEllipse(Im, {145, 60}, {155, 70}, Blue), 22 | 23 | egd:save(egd:render(Im, png), "/home/egil/test2.png"), 24 | 25 | R = 80, 26 | X0 = 99, 27 | Y0 = 99, 28 | 29 | Pts = [ { X0 + trunc(R*math:cos(A*math:pi()*2/360)), 30 | Y0 + trunc(R*math:sin(A*math:pi()*2/360)) 31 | } || A <- lists:seq(0,359,5)], 32 | lists:map( 33 | fun({X,Y}) -> 34 | egd:rectangle(Im, {X-5, Y-5}, {X+5,Y+5}, Green) 35 | end, Pts), 36 | 37 | egd:save(egd:render(Im, png), "/home/egil/test3.png"), 38 | 39 | % Text 40 | Filename = filename:join([code:priv_dir(egd), "fonts", "6x11_latin1.wingsfont"]), 41 | Font = egd_font:load(Filename), 42 | {W,H} = egd_font:size(Font), 43 | String = "egd says hello", 44 | Length = length(String), 45 | 46 | egd:text(Im, {round(100 - W*Length/2), 200 - H - 5}, Font, String, Black), 47 | 48 | egd:save(egd:render(Im, png), "/home/egil/test4.png"), 49 | 50 | egd:destroy(Im). 51 | -------------------------------------------------------------------------------- /doc/src/img_esi.erl: -------------------------------------------------------------------------------- 1 | -module(img_esi). 2 | 3 | -export([image/3]). 4 | 5 | image(SessionID, _Env, _Input) -> 6 | mod_esi:deliver(SessionID, header()), 7 | Binary = my_image(), 8 | mod_esi:deliver(SessionID, binary_to_list(Binary)). 9 | 10 | my_image() -> 11 | Im = egd:create(300,20), 12 | Black = egd:color({0,0,0}), 13 | Red = egd:color({255,0,0}), 14 | egd:filledRectangle(Im, {30,14}, {270,19}, Red), 15 | egd:rectangle(Im, {30,14}, {270,19}, Black), 16 | 17 | Filename = filename:join([code:priv_dir(percept), "fonts", "6x11_latin1.wingsfont"]), 18 | Font = egd_font:load(Filename), 19 | egd:text(Im, {30, 0}, Font, "egd with esi callback", Black), 20 | Bin = egd:render(Im, png), 21 | egd:destroy(Im), 22 | Bin. 23 | 24 | header() -> 25 | "Content-Type: image/png\r\n\r\n". 26 | -------------------------------------------------------------------------------- /doc/src/img_esi_result.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erlang/egd/1cea959544de7dd40a3284ba571a58939de57616/doc/src/img_esi_result.gif -------------------------------------------------------------------------------- /doc/src/notes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 2007 8 | 2016 9 | Ericsson AB, All Rights Reserved 10 | 11 | 12 | Licensed under the Apache License, Version 2.0 (the "License"); 13 | you may not use this file except in compliance with the License. 14 | You may obtain a copy of the License at 15 | 16 | http://www.apache.org/licenses/LICENSE-2.0 17 | 18 | Unless required by applicable law or agreed to in writing, software 19 | distributed under the License is distributed on an "AS IS" BASIS, 20 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 | See the License for the specific language governing permissions and 22 | limitations under the License. 23 | 24 | The Initial Developer of the Original Code is Ericsson AB. 25 | 26 | 27 | Erlang Graphics Draw Release Notes 28 | otp_appnotes 29 | nil 30 | nil 31 | nil 32 | notes.xml 33 |
34 |

This document describes the changes made to the Erlang Graphics Draw application.

35 | 36 |
37 | -------------------------------------------------------------------------------- /doc/src/part.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 2007 8 | 2016 9 | Ericsson AB, All Rights Reserved 10 | 11 | 12 | Licensed under the Apache License, Version 2.0 (the "License"); 13 | you may not use this file except in compliance with the License. 14 | You may obtain a copy of the License at 15 | 16 | http://www.apache.org/licenses/LICENSE-2.0 17 | 18 | Unless required by applicable law or agreed to in writing, software 19 | distributed under the License is distributed on an "AS IS" BASIS, 20 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 | See the License for the specific language governing permissions and 22 | limitations under the License. 23 | 24 | The Initial Developer of the Original Code is Ericsson AB. 25 | 26 | 27 | Percept User's Guide 28 | Björn-Egil Dahlberg 29 | 30 | 2007-11-02 31 | 0.5.0 32 | part.xml 33 |
34 | 35 |

36 | Percept is an acronym for Percept - erlang 37 | concurrency profiling tool. 38 |

39 |

40 | It is a tool to visualize application level concurrency and 41 | identify concurrency bottlenecks. 42 |

43 |
44 | 45 |
46 | -------------------------------------------------------------------------------- /doc/src/ref_man.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 2007 8 | 2016 9 | Ericsson AB, All Rights Reserved 10 | 11 | 12 | Licensed under the Apache License, Version 2.0 (the "License"); 13 | you may not use this file except in compliance with the License. 14 | You may obtain a copy of the License at 15 | 16 | http://www.apache.org/licenses/LICENSE-2.0 17 | 18 | Unless required by applicable law or agreed to in writing, software 19 | distributed under the License is distributed on an "AS IS" BASIS, 20 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 | See the License for the specific language governing permissions and 22 | limitations under the License. 23 | 24 | The Initial Developer of the Original Code is Ericsson AB. 25 | 26 | 27 | Percept Reference Manual 28 | Edoc 29 | 30 | 2007-11-02 31 | 1.0 32 | ref_man.xml 33 |
34 | 35 |

36 | Percept is an acronym for Percept - erlang 37 | concurrency profiling tool. 38 |

39 |

40 | It is a tool to visualize application level concurrency and 41 | identify concurrency bottlenecks. 42 |

43 |
44 | 45 |
46 | -------------------------------------------------------------------------------- /doc/src/test1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erlang/egd/1cea959544de7dd40a3284ba571a58939de57616/doc/src/test1.gif -------------------------------------------------------------------------------- /doc/src/test2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erlang/egd/1cea959544de7dd40a3284ba571a58939de57616/doc/src/test2.gif -------------------------------------------------------------------------------- /doc/src/test3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erlang/egd/1cea959544de7dd40a3284ba571a58939de57616/doc/src/test3.gif -------------------------------------------------------------------------------- /doc/src/test4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erlang/egd/1cea959544de7dd40a3284ba571a58939de57616/doc/src/test4.gif -------------------------------------------------------------------------------- /priv/fonts/6x11_latin1.wingsfont: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erlang/egd/1cea959544de7dd40a3284ba571a58939de57616/priv/fonts/6x11_latin1.wingsfont -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | %% -*- erlang -*- 2 | {plugins, [rebar3_appup_plugin]}. 3 | 4 | {provider_hooks, [{post, [{compile, {appup, compile}}, 5 | {clean, {appup, clean}}]} 6 | ]}. 7 | 8 | {otpdoc_opts, [{edoc_modules, [egd]}]}. 9 | 10 | %% vim: ft=erlang 11 | -------------------------------------------------------------------------------- /src/egd.app.src: -------------------------------------------------------------------------------- 1 | %% 2 | %% %CopyrightBegin% 3 | %% 4 | %% Copyright Ericsson AB 2007-2016. All Rights Reserved. 5 | %% 6 | %% Licensed under the Apache License, Version 2.0 (the "License"); 7 | %% you may not use this file except in compliance with the License. 8 | %% You may obtain a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, software 13 | %% distributed under the License is distributed on an "AS IS" BASIS, 14 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | %% See the License for the specific language governing permissions and 16 | %% limitations under the License. 17 | %% 18 | %% %CopyrightEnd% 19 | %% 20 | 21 | {application, egd, 22 | [{description, "Erlang Graphics Draw"}, 23 | {vsn, git}, 24 | {registered, []}, 25 | {applications, [kernel, 26 | stdlib]}, 27 | {env,[]}, 28 | {modules, [egd, 29 | egd_font, 30 | egd_png, 31 | egd_primitives, 32 | egd_render]}, 33 | {maintainers, []}, 34 | {licenses, []}, 35 | {links, []} 36 | ]}. 37 | 38 | %% vim: ft=erlang 39 | -------------------------------------------------------------------------------- /src/egd.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% %CopyrightBegin% 3 | %% 4 | %% Copyright Ericsson AB 2008-2016. All Rights Reserved. 5 | %% 6 | %% Licensed under the Apache License, Version 2.0 (the "License"); 7 | %% you may not use this file except in compliance with the License. 8 | %% You may obtain a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, software 13 | %% distributed under the License is distributed on an "AS IS" BASIS, 14 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | %% See the License for the specific language governing permissions and 16 | %% limitations under the License. 17 | %% 18 | %% %CopyrightEnd% 19 | 20 | %% 21 | %% @doc egd - erlang graphical drawer 22 | %% 23 | %% 24 | 25 | -module(egd). 26 | 27 | -export([create/2, destroy/1, information/1]). 28 | -export([text/5, line/4, color/1, color/2]). 29 | -export([rectangle/4, filledRectangle/4, filledEllipse/4]). 30 | -export([arc/4, arc/5]). 31 | -export([render/1, render/2, render/3]). 32 | 33 | -export([filledTriangle/5, polygon/3]). 34 | 35 | -export([save/2]). 36 | 37 | -include("egd.hrl"). 38 | 39 | %%========================================================================== 40 | %% Type definitions 41 | %%========================================================================== 42 | 43 | %% @type egd_image() 44 | %% @type font() 45 | %% @type point() = {integer(), integer()} 46 | %% @type color() 47 | %% @type render_option() = {render_engine, opaque} | {render_engine, alpha} 48 | 49 | -type egd_image() :: pid(). 50 | -type point() :: {non_neg_integer(), non_neg_integer()}. 51 | -type render_option() :: {'render_engine', 'opaque'} | {'render_engine', 'alpha'}. 52 | -type color() :: {float(), float(), float(), float()}. 53 | 54 | %%========================================================================== 55 | %% Interface functions 56 | %%========================================================================== 57 | 58 | %% @spec create(integer(), integer()) -> egd_image() 59 | %% @doc Creates an image area and returns its reference. 60 | 61 | -spec create(Width :: integer(), Height :: integer()) -> egd_image(). 62 | 63 | create(Width,Height) -> 64 | spawn_link(fun() -> init(trunc(Width),trunc(Height)) end). 65 | 66 | 67 | %% @spec destroy(egd_image()) -> ok 68 | %% @doc Destroys the image. 69 | 70 | -spec destroy(Image :: egd_image()) -> ok. 71 | 72 | destroy(Image) -> 73 | cast(Image, destroy). 74 | 75 | 76 | %% @spec render(egd_image()) -> binary() 77 | %% @equiv render(Image, png, [{render_engine, opaque}]) 78 | 79 | -spec render(Image :: egd_image()) -> binary(). 80 | 81 | render(Image) -> 82 | render(Image, png, [{render_engine, opaque}]). 83 | 84 | %% @spec render(egd_image(), png | raw_bitmap) -> binary() 85 | %% @equiv render(Image, Type, [{render_engine, opaque}]) 86 | 87 | render(Image, Type) -> 88 | render(Image, Type, [{render_engine, opaque}]). 89 | 90 | %% @spec render(egd_image(), png | raw_bitmap, [render_option()]) -> binary() 91 | %% @doc Renders a binary from the primitives specified by egd_image(). The 92 | %% binary can either be a raw bitmap with rgb tripplets or a binary in png 93 | %% format. 94 | 95 | -spec render( 96 | Image :: egd_image(), 97 | Type :: 'png' | 'raw_bitmap' | 'eps', 98 | Options :: [render_option()]) -> binary(). 99 | 100 | render(Image, Type, Options) -> 101 | {render_engine, RenderType} = proplists:lookup(render_engine, Options), 102 | call(Image, {render, Type, RenderType}). 103 | 104 | 105 | %% @spec information(egd_image()) -> ok 106 | %% @hidden 107 | %% @doc Writes out information about the image. This is a debug feature 108 | %% mainly. 109 | 110 | information(Pid) -> 111 | cast(Pid, information). 112 | 113 | %% @spec line(egd_image(), point(), point(), color()) -> ok 114 | %% @doc Creates a line object from P1 to P2 in the image. 115 | 116 | -spec line( 117 | Image :: egd_image(), 118 | P1 :: point(), 119 | P2 :: point(), 120 | Color :: color()) -> 'ok'. 121 | 122 | line(Image, P1, P2, Color) -> 123 | cast(Image, {line, P1, P2, Color}). 124 | 125 | %% @spec color( Value | Name ) -> color() 126 | %% where 127 | %% Value = {byte(), byte(), byte()} | {byte(), byte(), byte(), byte()} 128 | %% Name = black | silver | gray | white | maroon | red | purple | fuchia | green | lime | olive | yellow | navy | blue | teal | aqua 129 | %% @doc Creates a color reference. 130 | 131 | -spec color(Value :: {byte(), byte(), byte()} | {byte(), byte(), byte(), byte()} | atom()) -> 132 | color(). 133 | 134 | color(Color) -> 135 | egd_primitives:color(Color). 136 | 137 | %% @spec color(egd_image(), {byte(), byte(), byte()}) -> color() 138 | %% @doc Creates a color reference. 139 | %% @hidden 140 | 141 | color(_Image, Color) -> 142 | egd_primitives:color(Color). 143 | 144 | %% @spec text(egd_image(), point(), font(), string(), color()) -> ok 145 | %% @doc Creates a text object. 146 | 147 | text(Image, P, Font, Text, Color) -> 148 | cast(Image, {text, P, Font, Text, Color}). 149 | 150 | %% @spec rectangle(egd_image(), point(), point(), color()) -> ok 151 | %% @doc Creates a rectangle object. 152 | 153 | rectangle(Image, P1, P2, Color) -> 154 | cast(Image, {rectangle, P1, P2, Color}). 155 | 156 | %% @spec filledRectangle(egd_image(), point(), point(), color()) -> ok 157 | %% @doc Creates a filled rectangle object. 158 | 159 | filledRectangle(Image, P1, P2, Color) -> 160 | cast(Image, {filled_rectangle, P1, P2, Color}). 161 | 162 | %% @spec filledEllipse(egd_image(), point(), point(), color()) -> ok 163 | %% @doc Creates a filled ellipse object. 164 | 165 | filledEllipse(Image, P1, P2, Color) -> 166 | cast(Image, {filled_ellipse, P1, P2, Color}). 167 | 168 | %% @spec filledTriangle(egd_image(), point(), point(), point(), color()) -> ok 169 | %% @hidden 170 | %% @doc Creates a filled triangle object. 171 | 172 | filledTriangle(Image, P1, P2, P3, Color) -> 173 | cast(Image, {filled_triangle, P1, P2, P3, Color}). 174 | 175 | %% @spec polygon(egd_image(), [point()], color()) -> ok 176 | %% @hidden 177 | %% @doc Creates a filled filled polygon object. 178 | 179 | polygon(Image, Pts, Color) -> 180 | cast(Image, {polygon, Pts, Color}). 181 | 182 | %% @spec arc(egd_image(), point(), point(), color()) -> ok 183 | %% @hidden 184 | %% @doc Creates an arc with radius of bbx corner. 185 | 186 | arc(Image, P1, P2, Color) -> 187 | cast(Image, {arc, P1, P2, Color}). 188 | 189 | %% @spec arc(egd_image(), point(), point(), integer(), color()) -> ok 190 | %% @hidden 191 | %% @doc Creates an arc. 192 | 193 | arc(Image, P1, P2, D, Color) -> 194 | cast(Image, {arc, P1, P2, D, Color}). 195 | 196 | %% @spec save(binary(), string()) -> ok 197 | %% @doc Saves the binary to file. 198 | 199 | save(Binary, Filename) when is_binary(Binary) -> 200 | ok = file:write_file(Filename, Binary), 201 | ok. 202 | % --------------------------------- 203 | % Aux functions 204 | % --------------------------------- 205 | 206 | cast(Pid, Command) -> 207 | Pid ! {egd, self(), Command}, 208 | ok. 209 | 210 | call(Pid, Command) -> 211 | Pid ! {egd, self(), Command}, 212 | receive {egd, Pid, Result} -> Result end. 213 | 214 | % --------------------------------- 215 | % Server loop 216 | % --------------------------------- 217 | 218 | init(W,H) -> 219 | Image = egd_primitives:create(W,H), 220 | loop(Image). 221 | 222 | loop(Image) -> 223 | receive 224 | % Quitting 225 | {egd, _Pid, destroy} -> ok; 226 | 227 | % Rendering 228 | {egd, Pid, {render, BinaryType, RenderType}} -> 229 | case BinaryType of 230 | raw_bitmap -> 231 | Bitmap = egd_render:binary(Image, RenderType), 232 | Pid ! {egd, self(), Bitmap}, 233 | loop(Image); 234 | eps -> 235 | Eps = egd_render:eps(Image), 236 | Pid ! {egd, self(), Eps}, 237 | loop(Image); 238 | png -> 239 | Bitmap = egd_render:binary(Image, RenderType), 240 | Png = egd_png:binary( 241 | Image#image.width, 242 | Image#image.height, 243 | Bitmap), 244 | Pid ! {egd, self(), Png}, 245 | loop(Image); 246 | Unhandled -> 247 | Pid ! {egd, self(), {error, {format, Unhandled}}}, 248 | loop(Image) 249 | end; 250 | 251 | % Drawing primitives 252 | {egd, _Pid, {line, P1, P2, C}} -> 253 | loop(egd_primitives:line(Image, P1, P2, C)); 254 | {egd, _Pid, {text, P, Font, Text, C}} -> 255 | loop(egd_primitives:text(Image, P, Font, Text, C)); 256 | {egd, _Pid, {filled_ellipse, P1, P2, C}} -> 257 | loop(egd_primitives:filledEllipse(Image, P1, P2, C)); 258 | {egd, _Pid, {filled_rectangle, P1, P2, C}} -> 259 | loop(egd_primitives:filledRectangle(Image, P1, P2, C)); 260 | {egd, _Pid, {filled_triangle, P1, P2, P3, C}} -> 261 | loop(egd_primitives:filledTriangle(Image, P1, P2, P3, C)); 262 | {egd, _Pid, {polygon, Pts, C}} -> 263 | loop(egd_primitives:polygon(Image, Pts, C)); 264 | {egd, _Pid, {arc, P1, P2, C}} -> 265 | loop(egd_primitives:arc(Image, P1, P2, C)); 266 | {egd, _Pid, {arc, P1, P2, D, C}} -> 267 | loop(egd_primitives:arc(Image, P1, P2, D, C)); 268 | {egd, _Pid, {rectangle, P1, P2, C}} -> 269 | loop(egd_primitives:rectangle(Image, P1, P2, C)); 270 | {egd, _Pid, information} -> 271 | egd_primitives:info(Image), 272 | loop(Image); 273 | _ -> 274 | loop(Image) 275 | end. 276 | -------------------------------------------------------------------------------- /src/egd.hrl: -------------------------------------------------------------------------------- 1 | %% 2 | %% %CopyrightBegin% 3 | %% 4 | %% Copyright Ericsson AB 2008-2016. All Rights Reserved. 5 | %% 6 | %% Licensed under the Apache License, Version 2.0 (the "License"); 7 | %% you may not use this file except in compliance with the License. 8 | %% You may obtain a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, software 13 | %% distributed under the License is distributed on an "AS IS" BASIS, 14 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | %% See the License for the specific language governing permissions and 16 | %% limitations under the License. 17 | %% 18 | %% %CopyrightEnd% 19 | 20 | -type rgba_float() :: {float(), float(), float(), float()}. 21 | -type rgba_byte() :: {byte(), byte(), byte(), byte()}. 22 | -type rgb() :: {byte(), byte(), byte()}. 23 | 24 | -record(image_object, { 25 | type, 26 | points = [], 27 | span, 28 | internals, 29 | intervals, 30 | color}). % RGBA in float values 31 | 32 | -record(image, { 33 | width, 34 | height, 35 | objects = [], 36 | background = {1.0,1.0,1.0,1.0}, 37 | image}). 38 | 39 | -define(debug, void). 40 | 41 | -ifdef(debug). 42 | -define(dbg(X), io:format("DEBUG: ~p:~p~n",[?MODULE, X])). 43 | -else. 44 | -define(dbg(X), ok). 45 | -endif. 46 | -------------------------------------------------------------------------------- /src/egd_font.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% %CopyrightBegin% 3 | %% 4 | %% Copyright Ericsson AB 2008-2016. All Rights Reserved. 5 | %% 6 | %% Licensed under the Apache License, Version 2.0 (the "License"); 7 | %% you may not use this file except in compliance with the License. 8 | %% You may obtain a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, software 13 | %% distributed under the License is distributed on an "AS IS" BASIS, 14 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | %% See the License for the specific language governing permissions and 16 | %% limitations under the License. 17 | %% 18 | %% %CopyrightEnd% 19 | 20 | %% 21 | %% @doc egd_font 22 | %% 23 | 24 | -module(egd_font). 25 | 26 | -export([load/1, load_binary/1, size/1, glyph/2]). 27 | -include("egd.hrl"). 28 | 29 | %% Font represenatation in ets table 30 | %% egd_font_table 31 | %% 32 | %% Information: 33 | %% {Key, Description, Size} 34 | %% Key :: {Font :: atom(), information} 35 | %% Description :: any(), Description header from font file 36 | %% Size :: {W :: integer(), H :: integer()} 37 | %% 38 | %% Glyphs: 39 | %% {Key, Translation LSs} where 40 | %% Key :: {Font :: atom(), Code :: integer()}, Code = glyph char code 41 | %% Translation :: { 42 | %% W :: integer(), % BBx width 43 | %% H :: integer(), % BBx height 44 | %% X0 :: integer(), % X start 45 | %% Y0 :: integer(), % Y start 46 | %% Xm :: integer(), % Glyph X move when drawing 47 | %% } 48 | %% LSs :: [[{Xl :: integer(), Xr :: integer()}]] 49 | %% The first list is height (top to bottom), the inner list is the list 50 | %% of line spans for the glyphs horizontal pixels. 51 | %% 52 | 53 | %%========================================================================== 54 | %% Interface functions 55 | %%========================================================================== 56 | 57 | size(Font) -> 58 | [{_Key, _Description, Size}] = ets:lookup(egd_font_table,{Font,information}), 59 | Size. 60 | 61 | glyph(Font, Code) -> 62 | [{_Key, Translation, LSs}] = ets:lookup(egd_font_table,{Font,Code}), 63 | {Translation, LSs}. 64 | 65 | load(Filename) -> 66 | {ok, Bin} = file:read_file(Filename), 67 | load_binary(Bin). 68 | 69 | load_binary(Bin) when is_binary(Bin) -> 70 | Font = erlang:binary_to_term(Bin), 71 | load_font_header(Font). 72 | 73 | %%========================================================================== 74 | %% Internal functions 75 | %%========================================================================== 76 | 77 | %% ETS handler functions 78 | 79 | initialize_table() -> 80 | egd_font_table = ets:new(egd_font_table, [named_table, ordered_set, public]), 81 | ok. 82 | 83 | glyph_insert(Font, Code, Translation, LSs) -> 84 | Element = {{Font, Code}, Translation, LSs}, 85 | ets:insert(egd_font_table, Element). 86 | 87 | font_insert(Font, Description, Dimensions) -> 88 | Element = {{Font, information}, Description, Dimensions}, 89 | ets:insert(egd_font_table, Element). 90 | 91 | %% Font loader functions 92 | 93 | is_font_loaded(Font) -> 94 | try 95 | case ets:lookup(egd_font_table, {Font, information}) of 96 | [] -> false; 97 | _ -> true 98 | end 99 | catch 100 | error:_ -> 101 | initialize_table(), 102 | false 103 | end. 104 | 105 | 106 | load_font_header({_Type, _Version, Font}) -> 107 | load_font_body(Font). 108 | 109 | load_font_body({Key,Desc,W,H,Glyphs,Bitmaps}) -> 110 | case is_font_loaded(Key) of 111 | true -> Key; 112 | false -> 113 | % insert dimensions 114 | font_insert(Key, Desc, {W,H}), 115 | parse_glyphs(Glyphs, Bitmaps, Key), 116 | Key 117 | end. 118 | 119 | parse_glyphs([], _ , _Key) -> ok; 120 | parse_glyphs([Glyph|Glyphs], Bs, Key) -> 121 | {Code, Translation, LSs} = parse_glyph(Glyph, Bs), 122 | glyph_insert(Key, Code, Translation, LSs), 123 | parse_glyphs(Glyphs, Bs, Key). 124 | 125 | parse_glyph({Code,W,H,X0,Y0,Xm,Offset}, Bitmasks) -> 126 | BytesPerLine = ((W+7) div 8), 127 | NumBytes = BytesPerLine*H, 128 | <<_:Offset/binary,Bitmask:NumBytes/binary,_/binary>> = Bitmasks, 129 | LSs = render_glyph(W,H,X0,Y0,Xm,Bitmask), 130 | {Code, {W,H,X0,Y0,Xm}, LSs}. 131 | 132 | render_glyph(W, H, X0, Y0, Xm, Bitmask) -> 133 | render_glyph(W,{0,H},X0,Y0,Xm,Bitmask, []). 134 | render_glyph(_W, {H,H}, _X0, _Y0, _Xm, _Bitmask, Out) -> Out; 135 | render_glyph(W, {Hi,H}, X0, Y0,Xm, Bitmask , LSs) -> 136 | N = ((W+7) div 8), 137 | O = N*Hi, 138 | <<_:O/binary, Submask/binary>> = Bitmask, 139 | LS = render_glyph_horizontal( 140 | Submask, % line glyph bitmask 141 | {down, W - 1}, % loop state 142 | W - 1, % Width 143 | []), % Linespans 144 | render_glyph(W,{Hi+1,H},X0,Y0,Xm, Bitmask, [LS|LSs]). 145 | 146 | render_glyph_horizontal(Value, {Pr, Px}, 0, Spans) -> 147 | Cr = bit_spin(Value, 0), 148 | case {Pr,Cr} of 149 | {up , up } -> % closure of interval since its last 150 | [{0, Px}|Spans]; 151 | {up , down} -> % closure of interval 152 | [{1, Px}|Spans]; 153 | {down, up } -> % beginning of interval 154 | [{0, 0}|Spans]; 155 | {down, down} -> % no change in interval 156 | Spans 157 | end; 158 | render_glyph_horizontal(Value, {Pr, Px}, Cx, Spans) -> 159 | Cr = bit_spin(Value, Cx), 160 | case {Pr,Cr} of 161 | {up , up } -> % no change in interval 162 | render_glyph_horizontal(Value, {Cr, Px}, Cx - 1, Spans); 163 | {up , down} -> % closure of interval 164 | render_glyph_horizontal(Value, {Cr, Cx}, Cx - 1, [{Cx+1,Px}|Spans]); 165 | {down, up } -> % beginning of interval 166 | render_glyph_horizontal(Value, {Cr, Cx}, Cx - 1, Spans); 167 | {down, down} -> % no change in interval 168 | render_glyph_horizontal(Value, {Cr, Px}, Cx - 1, Spans) 169 | end. 170 | 171 | bit_spin(Value, Cx) -> 172 | <<_:Cx, Bit:1, _/bits>> = Value, 173 | case Bit of 174 | 1 -> up; 175 | 0 -> down 176 | end. 177 | -------------------------------------------------------------------------------- /src/egd_png.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% %CopyrightBegin% 3 | %% 4 | %% Copyright Ericsson AB 2008-2016. All Rights Reserved. 5 | %% 6 | %% Licensed under the Apache License, Version 2.0 (the "License"); 7 | %% you may not use this file except in compliance with the License. 8 | %% You may obtain a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, software 13 | %% distributed under the License is distributed on an "AS IS" BASIS, 14 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | %% See the License for the specific language governing permissions and 16 | %% limitations under the License. 17 | %% 18 | %% %CopyrightEnd% 19 | 20 | 21 | %% This code was originally written by Dan Gudmundsson for png-handling in 22 | %% wings3d (e3d__png). 23 | %% 24 | %% @doc egd 25 | %% 26 | 27 | -module(egd_png). 28 | 29 | -export([binary/3]). 30 | 31 | -include("egd.hrl"). 32 | 33 | -define(MAGIC, 137,$P,$N,$G,$\r,$\n,26,$\n). 34 | 35 | -define(GREYSCALE, 0). 36 | -define(TRUECOLOUR, 2). 37 | -define(INDEXED, 3). 38 | -define(GREYSCALE_A, 4). 39 | -define(TRUECOLOUR_A,6). 40 | 41 | -define(MAX_WBITS,15). 42 | 43 | -define(CHUNK, 240). 44 | 45 | -define(get4p1(Idx),((Idx) bsr 4)). 46 | -define(get4p2(Idx),((Idx) band 16#0F)). 47 | -define(get2p1(Idx),((Idx) bsr 6)). 48 | -define(get2p2(Idx),(((Idx) bsr 4) band 3)). 49 | -define(get2p3(Idx),(((Idx) bsr 2) band 3)). 50 | -define(get2p4(Idx),((Idx) band 3)). 51 | -define(get1p1(Idx),((Idx) bsr 7)). 52 | -define(get1p2(Idx),(((Idx) bsr 6) band 1)). 53 | -define(get1p3(Idx),(((Idx) bsr 5) band 1)). 54 | -define(get1p4(Idx),(((Idx) bsr 4) band 1)). 55 | -define(get1p5(Idx),(((Idx) bsr 3) band 1)). 56 | -define(get1p6(Idx),(((Idx) bsr 2) band 1)). 57 | -define(get1p7(Idx),(((Idx) bsr 1) band 1)). 58 | -define(get1p8(Idx),((Idx) band 1)). 59 | 60 | binary(W, H, Bitmap) when is_binary(Bitmap) -> 61 | Z = zlib:open(), 62 | Binary = bitmap2png(W, H, Bitmap, Z), 63 | zlib:close(Z), 64 | Binary. 65 | 66 | 67 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 68 | 69 | % Begin Tainted 70 | 71 | bitmap2png(W, H, Bitmap,Z) -> 72 | HDR = create_chunk(<<"IHDR",W:32,H:32,8:8,(png_type(r8g8b8)):8,0:8,0:8,0:8>>,Z), 73 | DATA = create_chunk(["IDAT",compress_image(0,3*W,Bitmap,[])],Z), 74 | END = create_chunk(<<"IEND">>,Z), 75 | list_to_binary([?MAGIC,HDR,DATA,END]). 76 | 77 | compress_image(I,RowLen, Bin, Acc) -> 78 | Pos = I*RowLen, 79 | case Bin of 80 | <<_:Pos/binary,Row:RowLen/binary,_/binary>> -> 81 | Filtered = filter_row(Row,RowLen), 82 | compress_image(I+1,RowLen,Bin,[Filtered|Acc]); 83 | _ when Pos == size(Bin) -> 84 | Filtered = list_to_binary(lists:reverse(Acc)), 85 | Compressed = zlib:compress(Filtered), 86 | Compressed 87 | end. 88 | 89 | filter_row(Row,_RowLen) -> 90 | [0,Row]. 91 | 92 | % dialyzer warnings 93 | %png_type(g8) -> ?GREYSCALE; 94 | %png_type(a8) -> ?GREYSCALE; 95 | %png_type(r8g8b8a8) -> ?TRUECOLOUR_A; 96 | png_type(r8g8b8) -> ?TRUECOLOUR. 97 | 98 | create_chunk(Bin,Z) when is_list(Bin) -> 99 | create_chunk(list_to_binary(Bin),Z); 100 | create_chunk(Bin,Z) when is_binary(Bin) -> 101 | Sz = size(Bin)-4, 102 | Crc = zlib:crc32(Z,Bin), 103 | <>. 104 | 105 | % End tainted 106 | -------------------------------------------------------------------------------- /src/egd_primitives.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% %CopyrightBegin% 3 | %% 4 | %% Copyright Ericsson AB 2008-2016. All Rights Reserved. 5 | %% 6 | %% Licensed under the Apache License, Version 2.0 (the "License"); 7 | %% you may not use this file except in compliance with the License. 8 | %% You may obtain a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, software 13 | %% distributed under the License is distributed on an "AS IS" BASIS, 14 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | %% See the License for the specific language governing permissions and 16 | %% limitations under the License. 17 | %% 18 | %% %CopyrightEnd% 19 | 20 | %% 21 | %% @doc egd_primitives 22 | %% 23 | 24 | 25 | -module(egd_primitives). 26 | -export([create/2, 27 | color/1, 28 | pixel/3, 29 | polygon/3, 30 | line/4, 31 | line/5, 32 | arc/4, 33 | arc/5, 34 | rectangle/4, 35 | filledRectangle/4, 36 | filledEllipse/4, 37 | filledTriangle/5, 38 | text/5]). 39 | 40 | -export([info/1, 41 | object_info/1, 42 | rgb_float2byte/1]). 43 | 44 | -export([arc_to_edges/3, 45 | convex_hull/1, 46 | edges/1]). 47 | 48 | -include("egd.hrl"). 49 | 50 | %% API info 51 | info(I) -> 52 | W = I#image.width, H = I#image.height, 53 | io:format("Dimensions: ~p x ~p~n", [W,H]), 54 | io:format("Number of image objects: ~p~n", [length(I#image.objects)]), 55 | TotalPoints = info_objects(I#image.objects,0), 56 | io:format("Total points: ~p [~p %]~n", [TotalPoints, 100*TotalPoints/(W*H)]), 57 | ok. 58 | 59 | info_objects([],N) -> N; 60 | info_objects([O | Os],N) -> 61 | Points = length(O#image_object.points), 62 | info_objects(Os,N+Points). 63 | 64 | object_info(O) -> 65 | io:format("Object information: ~p~n", [O#image_object.type]), 66 | io:format("- Number of points: ~p~n", [length(O#image_object.points)]), 67 | io:format("- Bounding box: ~p~n", [O#image_object.span]), 68 | io:format("- Color: ~p~n", [O#image_object.color]), 69 | ok. 70 | 71 | %% interface functions 72 | 73 | line(#image{objects=Os}=I, Sp, Ep, Color) -> 74 | line(#image{objects=Os}=I, Sp, Ep, 1, Color). 75 | 76 | line(#image{objects=Os}=I, Sp, Ep, Wd, Color) -> 77 | I#image{objects=[#image_object{ 78 | internals = Wd, 79 | type = line, 80 | points = [Sp, Ep], 81 | span = span([Sp, Ep]), 82 | color = Color}|Os]}. 83 | 84 | arc(I, {Sx,Sy} = Sp, {Ex,Ey} = Ep, Color) -> 85 | X = Ex - Sx, 86 | Y = Ey - Sy, 87 | R = math:sqrt(X*X + Y*Y)/2, 88 | arc(I, Sp, Ep, R, Color). 89 | 90 | arc(#image{objects=Os}=I, Sp, Ep, D, Color) -> 91 | SpanPts = lists:flatten([ 92 | [{X + D, Y + D}, 93 | {X + D, Y - D}, 94 | {X - D, Y + D}, 95 | {X - D, Y - D}] || {X,Y} <- [Sp,Ep]]), 96 | 97 | I#image{objects=[#image_object{ 98 | internals = D, 99 | type = arc, 100 | points = [Sp, Ep], 101 | span = span(SpanPts), 102 | color = Color}|Os]}. 103 | 104 | pixel(#image{objects=Os}=I, Point, Color) -> 105 | I#image{objects=[#image_object{ 106 | type = pixel, 107 | points = [Point], 108 | span = span([Point]), 109 | color = Color}|Os]}. 110 | 111 | rectangle(#image{objects=Os}=I, Sp, Ep, Color) -> 112 | I#image{objects=[#image_object{ 113 | type = rectangle, 114 | points = [Sp, Ep], 115 | span = span([Sp, Ep]), 116 | color = Color}|Os]}. 117 | 118 | filledRectangle(#image{objects=Os}=I, Sp, Ep, Color) -> 119 | I#image{objects=[#image_object{ 120 | type = filled_rectangle, 121 | points = [Sp, Ep], 122 | span = span([Sp, Ep]), 123 | color = Color}|Os]}. 124 | 125 | filledEllipse(#image{objects=Os}=I, Sp, Ep, Color) -> 126 | {X0,Y0,X1,Y1} = Span = span([Sp, Ep]), 127 | Xr = (X1 - X0)/2, 128 | Yr = (Y1 - Y0)/2, 129 | Xp = - X0 - Xr, 130 | Yp = - Y0 - Yr, 131 | I#image{objects=[#image_object{ 132 | internals = {Xp,Yp, Xr*Xr,Yr*Yr}, 133 | type = filled_ellipse, 134 | points = [Sp, Ep], 135 | span = Span, 136 | color = Color}|Os]}. 137 | 138 | filledTriangle(#image{objects=Os}=I, P1, P2, P3, Color) -> 139 | I#image{objects=[#image_object{ 140 | type = filled_triangle, 141 | points = [P1,P2,P3], 142 | span = span([P1,P2,P3]), 143 | color = Color}|Os]}. 144 | 145 | polygon(#image{objects=Os}=I, Points, Color) -> 146 | I#image{objects=[#image_object{ 147 | type = polygon, 148 | points = Points, 149 | span = span(Points), 150 | color = Color}|Os]}. 151 | 152 | text(#image{objects=Os}=I, {Xs,Ys}=Sp, Font, Text, Color) -> 153 | {FW,FH} = egd_font:size(Font), 154 | Length = length(Text), 155 | Ep = {Xs + Length*FW, Ys + FH + 5}, 156 | I#image{objects=[#image_object{ 157 | internals = {Font, Text}, 158 | type = text_horizontal, 159 | points = [Sp], 160 | span = span([Sp,Ep]), 161 | color = Color}|Os]}. 162 | 163 | create(W, H) -> 164 | #image{width = W, height = H}. 165 | 166 | color(Color) when is_atom(Color) -> rgba_byte2float(name_to_color(Color, 255)); 167 | color({Color, A}) when is_atom(Color) -> rgba_byte2float(name_to_color(Color, A)); 168 | color({R,G,B}) -> rgba_byte2float({R,G,B, 255}); 169 | color(C) -> rgba_byte2float(C). 170 | 171 | name_to_color(Color, A) -> 172 | case Color of 173 | %% HTML default colors 174 | black -> { 0, 0, 0, A}; 175 | silver -> {192, 192, 192, A}; 176 | gray -> {128, 128, 128, A}; 177 | white -> {128, 0, 0, A}; 178 | maroon -> {255, 0, 0, A}; 179 | red -> {128, 0, 128, A}; 180 | purple -> {128, 0, 128, A}; 181 | fuchia -> {255, 0, 255, A}; 182 | green -> { 0, 128, 0, A}; 183 | lime -> { 0, 255, 0, A}; 184 | olive -> {128, 128, 0, A}; 185 | yellow -> {255, 255, 0, A}; 186 | navy -> { 0, 0, 128, A}; 187 | blue -> { 0, 0, 255, A}; 188 | teal -> { 0, 128, 0, A}; 189 | aqua -> { 0, 255, 155, A}; 190 | 191 | %% HTML color extensions 192 | steelblue -> { 70, 130, 180, A}; 193 | royalblue -> { 4, 22, 144, A}; 194 | cornflowerblue -> {100, 149, 237, A}; 195 | lightsteelblue -> {176, 196, 222, A}; 196 | mediumslateblue -> {123, 104, 238, A}; 197 | slateblue -> {106, 90, 205, A}; 198 | darkslateblue -> { 72, 61, 139, A}; 199 | midnightblue -> { 25, 25, 112, A}; 200 | darkblue -> { 0, 0, 139, A}; 201 | mediumblue -> { 0, 0, 205, A}; 202 | dodgerblue -> { 30, 144, 255, A}; 203 | deepskyblue -> { 0, 191, 255, A}; 204 | lightskyblue -> {135, 206, 250, A}; 205 | skyblue -> {135, 206, 235, A}; 206 | lightblue -> {173, 216, 230, A}; 207 | powderblue -> {176, 224, 230, A}; 208 | azure -> {240, 255, 255, A}; 209 | lightcyan -> {224, 255, 255, A}; 210 | paleturquoise -> {175, 238, 238, A}; 211 | mediumturquoise -> { 72, 209, 204, A}; 212 | lightseagreen -> { 32, 178, 170, A}; 213 | darkcyan -> { 0, 139, 139, A}; 214 | cadetblue -> { 95, 158, 160, A}; 215 | darkturquoise -> { 0, 206, 209, A}; 216 | cyan -> { 0, 255, 255, A}; 217 | turquoise -> { 64, 224, 208, A}; 218 | aquamarine -> {127, 255, 212, A}; 219 | mediumaquamarine -> {102, 205, 170, A}; 220 | darkseagreen -> {143, 188, 143, A}; 221 | mediumseagreen -> { 60, 179, 113, A}; 222 | seagreen -> { 46, 139, 87, A}; 223 | darkgreen -> { 0, 100, 0, A}; 224 | forestgreen -> { 34, 139, 34, A}; 225 | limegreen -> { 50, 205, 50, A}; 226 | chartreuse -> {127, 255, 0, A}; 227 | lawngreen -> {124, 252, 0, A}; 228 | greenyellow -> {173, 255, 47, A}; 229 | yellowgreen -> {154, 205, 50, A}; 230 | palegreen -> {152, 251, 152, A}; 231 | lightgreen -> {144, 238, 144, A}; 232 | springgreen -> { 0, 255, 127, A}; 233 | darkolivegreen -> { 85, 107, 47, A}; 234 | olivedrab -> {107, 142, 35, A}; 235 | darkkhaki -> {189, 183, 107, A}; 236 | darkgoldenrod -> {184, 134, 11, A}; 237 | goldenrod -> {218, 165, 32, A}; 238 | gold -> {255, 215, 0, A}; 239 | khaki -> {240, 230, 140, A}; 240 | palegoldenrod -> {238, 232, 170, A}; 241 | blanchedalmond -> {255, 235, 205, A}; 242 | moccasin -> {255, 228, 181, A}; 243 | wheat -> {245, 222, 179, A}; 244 | navajowhite -> {255, 222, 173, A}; 245 | burlywood -> {222, 184, 135, A}; 246 | tan -> {210, 180, 140, A}; 247 | rosybrown -> {188, 143, 143, A}; 248 | sienna -> {160, 82, 45, A}; 249 | saddlebrown -> {139, 69, 19, A}; 250 | chocolate -> {210, 105, 30, A}; 251 | peru -> {205, 133, 63, A}; 252 | sandybrown -> {244, 164, 96, A}; 253 | darkred -> {139, 0, 0, A}; 254 | brown -> {165, 42, 42, A}; 255 | firebrick -> {178, 34, 34, A}; 256 | indianred -> {205, 92, 92, A}; 257 | lightcoral -> {240, 128, 128, A}; 258 | salmon -> {250, 128, 114, A}; 259 | darksalmon -> {233, 150, 122, A}; 260 | lightsalmon -> {255, 160, 122, A}; 261 | coral -> {255, 127, 80, A}; 262 | tomato -> {255, 99, 71, A}; 263 | darkorange -> {255, 140, 0, A}; 264 | orange -> {255, 165, 0, A}; 265 | orangered -> {255, 69, 0, A}; 266 | crimson -> {220, 20, 60, A}; 267 | deeppink -> {255, 20, 147, A}; 268 | fuchsia -> {255, 0, 255, A}; 269 | magenta -> {255, 0, 255, A}; 270 | hotpink -> {255, 105, 180, A}; 271 | lightpink -> {255, 182, 193, A}; 272 | pink -> {255, 192, 203, A}; 273 | palevioletred -> {219, 112, 147, A}; 274 | mediumvioletred -> {199, 21, 133, A}; 275 | darkmagenta -> {139, 0, 139, A}; 276 | mediumpurple -> {147, 112, 219, A}; 277 | blueviolet -> {138, 43, 226, A}; 278 | indigo -> { 75, 0, 130, A}; 279 | darkviolet -> {148, 0, 211, A}; 280 | darkorchid -> {153, 50, 204, A}; 281 | mediumorchid -> {186, 85, 211, A}; 282 | orchid -> {218, 112, 214, A}; 283 | violet -> {238, 130, 238, A}; 284 | plum -> {221, 160, 221, A}; 285 | thistle -> {216, 191, 216, A}; 286 | lavender -> {230, 230, 250, A}; 287 | ghostwhite -> {248, 248, 255, A}; 288 | aliceblue -> {240, 248, 255, A}; 289 | mintcream -> {245, 255, 250, A}; 290 | honeydew -> {240, 255, 240, A}; 291 | lemonchiffon -> {255, 250, 205, A}; 292 | cornsilk -> {255, 248, 220, A}; 293 | lightyellow -> {255, 255, 224, A}; 294 | ivory -> {255, 255, 240, A}; 295 | floralwhite -> {255, 250, 240, A}; 296 | linen -> {250, 240, 230, A}; 297 | oldlace -> {253, 245, 230, A}; 298 | antiquewhite -> {250, 235, 215, A}; 299 | bisque -> {255, 228, 196, A}; 300 | peachpuff -> {255, 218, 185, A}; 301 | papayawhip -> {255, 239, 213, A}; 302 | beige -> {245, 245, 220, A}; 303 | seashell -> {255, 245, 238, A}; 304 | lavenderblush -> {255, 240, 245, A}; 305 | mistyrose -> {255, 228, 225, A}; 306 | snow -> {255, 250, 250, A}; 307 | whitesmoke -> {245, 245, 245, A}; 308 | gainsboro -> {220, 220, 220, A}; 309 | lightgrey -> {211, 211, 211, A}; 310 | darkgray -> {169, 169, 169, A}; 311 | lightslategray -> {119, 136, 153, A}; 312 | slategray -> {112, 128, 144, A}; 313 | dimgray -> {105, 105, 105, A}; 314 | darkslategray -> { 47, 79, 79, A}; 315 | mediumspringgreen -> { 0, 250, 154, A}; 316 | lightgoldenrodyellow -> {250, 250, 210, A} 317 | end. 318 | 319 | 320 | %%% Generic transformations 321 | 322 | %% arc_to_edges 323 | %% In: 324 | %% P1 :: point(), 325 | %% P2 :: point(), 326 | %% D :: float(), 327 | %% Out: 328 | %% Res :: [edges()] 329 | 330 | arc_to_edges(P0, P1, D) when abs(D) < 0.5 -> [{P0,P1}]; 331 | arc_to_edges({X0,Y0}, {X1,Y1}, D) -> 332 | Vx = X1 - X0, 333 | Vy = Y1 - Y0, 334 | 335 | Mx = X0 + 0.5 * Vx, 336 | My = Y0 + 0.5 * Vy, 337 | 338 | % Scale V by Rs 339 | L = math:sqrt(Vx*Vx + Vy*Vy), 340 | Sx = D*Vx/L, 341 | Sy = D*Vy/L, 342 | 343 | Bx = trunc(Mx - Sy), 344 | By = trunc(My + Sx), 345 | 346 | arc_to_edges({X0,Y0}, {Bx,By}, D/4) ++ arc_to_edges({Bx,By}, {X1,Y1}, D/4). 347 | 348 | %% edges 349 | %% In: 350 | %% Pts :: [point()] 351 | %% Out: 352 | %% Edges :: [{point(),point()}] 353 | 354 | edges([]) -> []; 355 | edges([P0|_] = Pts) -> edges(Pts, P0,[]). 356 | edges([P1], P0, Out) -> [{P1,P0}|Out]; 357 | edges([P1,P2|Pts],P0,Out) -> edges([P2|Pts],P0,[{P1,P2}|Out]). 358 | 359 | %% convex_hull 360 | %% In: 361 | %% Ps :: [point()] 362 | %% Out: 363 | %% Res :: [point()] 364 | 365 | convex_hull(Ps) -> 366 | P0 = lower_right(Ps), 367 | [P1|Ps1] = lists:sort(fun 368 | (P2,P1) -> 369 | case point_side({P1,P0},P2) of 370 | left -> true; 371 | _ -> false 372 | end 373 | end, Ps -- [P0]), 374 | convex_hull(Ps1, [P1,P0]). 375 | 376 | convex_hull([], W) -> W; 377 | convex_hull([P|Pts], [P1,P2|W]) -> 378 | case point_side({P2,P1},P) of 379 | left -> convex_hull(Pts, [P,P1,P2|W]); 380 | _ -> convex_hull([P|Pts], [P2|W]) 381 | end. 382 | 383 | lower_right([P|Pts]) -> lower_right(P, Pts). 384 | lower_right(P, []) -> P; 385 | lower_right({X0,Y0}, [{_,Y}|Pts]) when Y < Y0 -> lower_right({X0,Y0}, Pts); 386 | lower_right({X0,Y0}, [{X,Y}|Pts]) when X < X0, Y < Y0 -> lower_right({X0,Y0}, Pts); 387 | lower_right(_,[P|Pts]) -> lower_right(P, Pts). 388 | 389 | point_side({{X0,Y0}, {X1, Y1}}, {X2, Y2}) -> point_side((X1 - X0)*(Y2 - Y0) - (X2 - X0)*(Y1 - Y0)). 390 | point_side(D) when D > 0 -> left; 391 | point_side(D) when D < 0 -> right; 392 | point_side(_) -> on_line. 393 | 394 | %% AUX 395 | 396 | span([{X0,Y0}|Points]) -> 397 | span(Points,X0,Y0,X0,Y0). 398 | span([{X0,Y0}|Points],Xmin,Ymin,Xmax,Ymax) -> 399 | span(Points,erlang:min(Xmin,X0), 400 | erlang:min(Ymin,Y0), 401 | erlang:max(Xmax,X0), 402 | erlang:max(Ymax,Y0)); 403 | span([],Xmin,Ymin,Xmax,Ymax) -> 404 | {Xmin,Ymin,Xmax,Ymax}. 405 | 406 | 407 | rgb_float2byte({R,G,B}) -> rgb_float2byte({R,G,B,1.0}); 408 | rgb_float2byte({R,G,B,A}) -> 409 | {trunc(R*255), trunc(G*255), trunc(B*255), trunc(A*255)}. 410 | 411 | rgba_byte2float({R,G,B,A}) -> 412 | {R/255,G/255,B/255,A/255}. 413 | -------------------------------------------------------------------------------- /src/egd_render.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% %CopyrightBegin% 3 | %% 4 | %% Copyright Ericsson AB 2008-2016. All Rights Reserved. 5 | %% 6 | %% Licensed under the Apache License, Version 2.0 (the "License"); 7 | %% you may not use this file except in compliance with the License. 8 | %% You may obtain a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, software 13 | %% distributed under the License is distributed on an "AS IS" BASIS, 14 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | %% See the License for the specific language governing permissions and 16 | %% limitations under the License. 17 | %% 18 | %% %CopyrightEnd% 19 | 20 | %% 21 | %% @doc egd_render 22 | %% 23 | 24 | -module(egd_render). 25 | 26 | -export([binary/1, binary/2]). 27 | -export([eps/1]). 28 | -compile(inline). 29 | 30 | -export([line_to_linespans/3]). 31 | 32 | -include("egd.hrl"). 33 | -define('DummyC',0). 34 | 35 | binary(Image) -> 36 | binary(Image, opaque). 37 | 38 | binary(Image, Type) -> 39 | parallel_binary(precompile(Image),Type). 40 | 41 | parallel_binary(Image = #image{ height = Height },Type) -> 42 | case erlang:min(erlang:system_info(schedulers), Height) of 43 | 1 -> 44 | % if the height or the number of schedulers is 1 45 | % do the scanlines in this process. 46 | W = Image#image.width, 47 | Bg = Image#image.background, 48 | Os = Image#image.objects, 49 | erlang:list_to_binary([scanline(Y, Os, {0,0,W - 1, Bg}, Type) 50 | || Y <- lists:seq(1, Height)]); 51 | Np -> 52 | Pids = start_workers(Np, Type), 53 | Handler = handle_workers(Height, Pids), 54 | init_workers(Image, Handler, Pids), 55 | Res = receive_binaries(Height), 56 | finish_workers(Pids), 57 | Res 58 | end. 59 | 60 | start_workers(Np, Type) -> 61 | start_workers(Np, Type, []). 62 | 63 | start_workers( 0, _, Pids) -> Pids; 64 | start_workers(Np, Type, Pids) when Np > 0 -> 65 | start_workers(Np - 1, Type, [spawn_link(fun() -> worker(Type) end)|Pids]). 66 | 67 | worker(Type) -> 68 | receive 69 | {Pid, data, #image{ objects = Os, width = W, background = Bg }} -> 70 | worker(Os, W, Bg, Type, Pid) 71 | end. 72 | 73 | worker(Objects, Width, Bg, Type, Collector) -> 74 | receive 75 | {Pid, scan, {Ys, Ye}} -> 76 | lists:foreach(fun 77 | (Y) -> 78 | Bin = erlang:list_to_binary(scanline(Y, Objects, {0,0,Width - 1, Bg}, Type)), 79 | Collector ! {scan, Y, Bin} 80 | end, lists:seq(Ys,Ye)), 81 | Pid ! {self(), scan_complete}, 82 | worker(Objects, Width, Bg, Type, Collector); 83 | {Pid, scan, Y} -> 84 | Bin = erlang:list_to_binary(scanline(Y, Objects, {0,0,Width - 1, Bg}, Type)), 85 | Collector ! {scan, Y, Bin}, 86 | Pid ! {self(), scan_complete}, 87 | worker(Objects, Width, Bg, Type, Collector); 88 | {_, done} -> 89 | ok 90 | end. 91 | 92 | init_workers(_Image, _Handler, []) -> ok; 93 | init_workers(Image, Handler, [Pid|Pids]) -> 94 | Pid ! {self(), data, Image}, 95 | Handler ! {Pid, scan_complete}, 96 | init_workers(Image, Handler, Pids). 97 | 98 | handle_workers(H, Pids) -> 99 | spawn_link(fun() -> handle_workers(H, H, length(Pids)) end). 100 | 101 | handle_workers(_, 0, _) -> ok; 102 | handle_workers(H, Hi, Np) when H > 0 -> 103 | N = trunc(Hi/(2*Np)), 104 | receive 105 | {Pid, scan_complete} -> 106 | if N < 2 -> 107 | Pid ! {self(), scan, Hi}, 108 | handle_workers(H, Hi - 1, Np); 109 | true -> 110 | Pid ! {self(), scan, {Hi - N, Hi}}, 111 | handle_workers(H, Hi - 1 - N, Np) 112 | end 113 | end. 114 | 115 | finish_workers([]) -> ok; 116 | finish_workers([Pid|Pids]) -> 117 | Pid ! {self(), done}, 118 | finish_workers(Pids). 119 | 120 | receive_binaries(H) -> 121 | receive_binaries(H, []). 122 | 123 | receive_binaries(0, Bins) -> erlang:list_to_binary(Bins); 124 | receive_binaries(H, Bins) when H > 0 -> 125 | receive 126 | {scan, H, Bin} -> 127 | receive_binaries(H - 1, [Bin|Bins]) 128 | end. 129 | 130 | scanline(Y, Os, {_,_,Width,_}=LSB, Type) -> 131 | OLSs = parse_objects_on_line(Y-1, Width, Os), 132 | RLSs = resulting_line_spans([LSB|OLSs],Type), 133 | [ lists:duplicate(Xr - Xl + 1, <<(trunc(R*255)):8,(trunc(G*255)):8,(trunc(B*255)):8>>) || {_,Xl, Xr, {R,G,B,_}} <- RLSs ]. 134 | 135 | resulting_line_spans(LSs,Type) -> 136 | %% Build a list of "transitions" from left to right. 137 | Trans = line_spans_to_trans(LSs), 138 | %% Convert list of "transitions" to linespans. 139 | trans_to_line_spans(Trans,Type). 140 | 141 | line_spans_to_trans(LSs) -> 142 | line_spans_to_trans(LSs,[],0). 143 | 144 | line_spans_to_trans([],Db,_) -> 145 | lists:sort(Db); 146 | line_spans_to_trans([{_,L,R,C}|LSs],Db,Z) -> 147 | line_spans_to_trans(LSs,[{{L,Z,start},C},{{R+1,Z,stop},C}|Db],Z+1). 148 | 149 | trans_to_line_spans(Trans,Type) -> 150 | trans_to_line_spans(simplify_trans(Trans,Type,[],{0.0,0.0,0.0,0.0},[])). 151 | 152 | trans_to_line_spans(SimpleTrans) -> 153 | trans_to_line_spans1(SimpleTrans,[]). 154 | 155 | trans_to_line_spans1([],Spans) -> 156 | Spans; 157 | trans_to_line_spans1([_],Spans) -> 158 | Spans; 159 | trans_to_line_spans1([{L1,_},{L2,C2}|SimpleTrans],Spans) -> 160 | %% We are going backwards now... 161 | trans_to_line_spans1([{L2,C2}|SimpleTrans],[{?DummyC,L2,L1-1,C2}|Spans]). 162 | 163 | simplify_trans([],_,_,_,Acc) -> 164 | Acc; 165 | simplify_trans([{{L,_,_},_}|_] = Trans,Type,Layers,OldC,Acc) -> 166 | {NextTrans,RestTrans} = 167 | lists:splitwith(fun({{L1,_,_},_}) when L1 == L -> 168 | true; 169 | (_) -> 170 | false 171 | end, Trans), 172 | {C,NewLayers} = color(NextTrans,Layers,Type,OldC), 173 | case OldC of 174 | C -> %% No change in color, so transition unnecessary. 175 | simplify_trans(RestTrans,Type,NewLayers,OldC,Acc); 176 | _ -> 177 | simplify_trans(RestTrans,Type,NewLayers,C,[{L,C}|Acc]) 178 | end. 179 | 180 | color(Trans,Layers,Type,OldC) -> 181 | case modify_layers(Layers,Trans) of 182 | Layers -> 183 | {OldC,Layers}; 184 | NewLayers -> 185 | {color(NewLayers,Type),NewLayers} 186 | end. 187 | 188 | color([],_) -> {0.0,0.0,0.0,0.0}; 189 | color([{_,C}|_],opaque) -> C; 190 | color(Layers,alpha) -> color1({0.0,0.0,0.0,0.0},Layers). 191 | 192 | color1(Color,[]) -> Color; 193 | color1(Color,[{_,C}|Layers]) -> color1(alpha_blend(Color,C),Layers). 194 | 195 | modify_layers(Layers,[]) -> Layers; 196 | modify_layers(Layers,[{{_,Z,start},C}|Trans]) -> 197 | modify_layers(add_layer(Layers, Z, C), Trans); 198 | modify_layers(Layers,[{{_,Z,stop },C}|Trans]) -> 199 | modify_layers(remove_layer(Layers, Z, C), Trans). 200 | 201 | add_layer([{Z1,_}=H|Layers],Z,C) when Z1 > Z -> 202 | [H|add_layer(Layers,Z,C)]; 203 | add_layer(Layers,Z,C) -> 204 | [{Z,C}|Layers]. 205 | 206 | remove_layer(Layers,Z,C) -> 207 | Layers -- [{Z,C}]. 208 | 209 | alpha_blend({R1,G1,B1,A1}, {R2,G2,B2,A2}) when is_float(A1), is_float(A2)-> 210 | Beta = A2*(1.0 - A1), 211 | A = A1 + Beta, 212 | R = R1*A1 + R2*Beta, 213 | G = G1*A1 + G2*Beta, 214 | B = B1*A1 + B2*Beta, 215 | {R,G,B,A}. 216 | 217 | parse_objects_on_line(Y, Width, Objects) -> 218 | parse_objects_on_line(Y, 1, Width, Objects, []). 219 | parse_objects_on_line(_Y, _Z, _, [], Out) -> lists:flatten(Out); 220 | parse_objects_on_line(Y, Z, Width, [O|Os], Out) -> 221 | case is_object_on_line(O, Y) of 222 | false -> 223 | parse_objects_on_line(Y, Z + 1, Width, Os, Out); 224 | true -> 225 | OLs = object_line_data(O,Y,Z), 226 | TOLs = trim_object_line_data(OLs, Width), 227 | parse_objects_on_line(Y, Z + 1, Width, Os, [TOLs|Out]) 228 | end. 229 | 230 | trim_object_line_data(OLs, Width) -> 231 | trim_object_line_data(OLs, Width, []). 232 | trim_object_line_data([], _, Out) -> Out; 233 | 234 | trim_object_line_data([{_, Xl, _, _}|OLs], Width, Out) when Xl > Width -> 235 | trim_object_line_data(OLs, Width, Out); 236 | trim_object_line_data([{_, _, Xr, _}|OLs], Width, Out) when Xr < 0 -> 237 | trim_object_line_data(OLs, Width, Out); 238 | trim_object_line_data([{Z, Xl, Xr, C}|OLs], Width, Out) -> 239 | trim_object_line_data(OLs, Width, [{Z, erlang:max(0,Xl), erlang:min(Xr,Width), C}|Out]). 240 | 241 | % object_line_data 242 | % In: 243 | % Object :: image_object() 244 | % Y :: index of height 245 | % Z :: index of depth 246 | % Out: 247 | % OLs = [{Z, Xl, Xr, Color}] 248 | % Z = index of height 249 | % Xl = left X index 250 | % Xr = right X index 251 | % Purpose: 252 | % Calculate the length (start and finish index) of an objects horizontal 253 | % line given the height index. 254 | 255 | object_line_data(#image_object{type=rectangle, 256 | span={X0,Y0,X1,Y1}, color=C}, Y, Z) -> 257 | if 258 | Y0 =:= Y ; Y1 =:= Y -> 259 | [{Z, X0, X1, C}]; 260 | true -> 261 | [{Z, X0, X0, C}, 262 | {Z, X1, X1, C}] 263 | end; 264 | 265 | object_line_data(#image_object{type=filled_rectangle, 266 | span={X0, _, X1, _}, color=C}, _Y, Z) -> 267 | [{Z, X0, X1, C}]; 268 | 269 | object_line_data(#image_object{type=filled_ellipse, 270 | internals={Xr,Yr,Yr2}, span={X0,Y0,X1,Y1}, color=C}, Y, Z) -> 271 | if 272 | X1 - X0 =:= 0; Y1 - Y0 =:= 0 -> 273 | [{Z, X0, X1, C}]; 274 | true -> 275 | Yo = trunc(Y - Y0 - Yr), 276 | Yo2 = Yo*Yo, 277 | Xo = math:sqrt((1 - Yo2/Yr2))*Xr, 278 | [{Z, round(X0 - Xo + Xr), round(X0 + Xo + Xr), C}] 279 | end; 280 | 281 | object_line_data(#image_object{type=filled_triangle, 282 | intervals=Is, color=C}, Y, Z) -> 283 | case lists:keyfind(Y, 1, Is) of 284 | {Y, Xl, Xr} -> [{Z, Xl, Xr, C}]; 285 | false -> [] 286 | end; 287 | 288 | object_line_data(#image_object{type=line, 289 | intervals=M, color={R,G,B,_}}, Y, Z) -> 290 | case M of 291 | #{Y := Ls} -> [{Z, Xl, Xr, {R,G,B,1.0-C/255}}||{Xl,Xr,C} <- Ls]; 292 | _ -> [] 293 | end; 294 | 295 | object_line_data(#image_object{type=polygon, 296 | color=C, intervals=Is}, Y, Z) -> 297 | [{Z, Xl, Xr, C} || {Yp, Xl, Xr} <- Is, Yp =:= Y]; 298 | 299 | object_line_data(#image_object{type=text_horizontal, 300 | color=C, intervals=Is}, Y, Z) -> 301 | [{Z, Xl, Xr, C} || {Yg, Xl, Xr} <- Is, Yg =:= Y]; 302 | 303 | object_line_data(#image_object{type=pixel, 304 | span={X0,_,X1,_}, color=C}, _, Z) -> 305 | [{Z, X0, X1, C}]. 306 | 307 | is_object_on_line(#image_object{span={_,Y0,_,Y1}}, Y) -> 308 | if Y < Y0; Y > Y1 -> false; 309 | true -> true 310 | end. 311 | 312 | %%% primitives to line_spans 313 | 314 | %% compile objects to linespans 315 | 316 | precompile(#image{objects = Os}=I) -> 317 | I#image{objects = precompile_objects(Os)}. 318 | 319 | precompile_objects([]) -> []; 320 | precompile_objects([#image_object{type=line, internals=W, points=[P0,P1]}=O|Os]) -> 321 | [O#image_object{intervals = linespans_to_map(line_to_linespans(P0,P1,W))}|precompile_objects(Os)]; 322 | precompile_objects([#image_object{type=filled_triangle, points=[P0,P1,P2]}=O|Os]) -> 323 | [O#image_object{intervals = triangle_ls(P0,P1,P2)}|precompile_objects(Os)]; 324 | precompile_objects([#image_object{type=polygon, points=Pts}=O|Os]) -> 325 | [O#image_object{intervals = polygon_ls(Pts)}|precompile_objects(Os)]; 326 | precompile_objects([#image_object{type=filled_ellipse, span={X0,Y0,X1,Y1}}=O|Os]) -> 327 | Xr = (X1 - X0)/2, 328 | Yr = (Y1 - Y0)/2, 329 | Yr2 = Yr*Yr, 330 | [O#image_object{internals={Xr,Yr,Yr2}}|precompile_objects(Os)]; 331 | precompile_objects([#image_object{type=arc, points=[P0,P1], internals=D}=O|Os]) -> 332 | Es = egd_primitives:arc_to_edges(P0, P1, D), 333 | Ls = lists:foldl(fun ({Ep0,Ep1},M) -> 334 | linespans_to_map(line_to_linespans(Ep0,Ep1,1),M) 335 | end, #{}, Es), 336 | [O#image_object{type=line, intervals=Ls}|precompile_objects(Os)]; 337 | precompile_objects([#image_object{type=text_horizontal, 338 | points=[P0], internals={Font,Text}}=O|Os]) -> 339 | [O#image_object{intervals=text_horizontal_ls(P0,Font,Text)}|precompile_objects(Os)]; 340 | precompile_objects([O|Os]) -> 341 | [O|precompile_objects(Os)]. 342 | 343 | % triangle 344 | 345 | triangle_ls(P1,P2,P3) -> 346 | % Find top point (or left most top point), 347 | % From that point, two lines will be drawn to the 348 | % other points. 349 | % For each Y step, 350 | % bresenham_line_interval for each of the two lines 351 | % Find the left most and the right most for those lines 352 | % At an end point, a new line to the point already being drawn 353 | % repeat same procedure as above 354 | [Sp1, Sp2, Sp3] = tri_pt_ysort([P1,P2,P3]), 355 | triangle_ls_lp(tri_ls_ysort(line_to_linespans(Sp1,Sp2,1)), Sp2, 356 | tri_ls_ysort(line_to_linespans(Sp1,Sp3,1)), Sp3, []). 357 | 358 | % There will be Y mismatches between the two lists since bresenham is not perfect. 359 | % I can be remedied with checking intervals this could however be costly and 360 | % it may not be necessary, depending on how exact we need the points to be. 361 | % It should at most differ by one and endpoints should be fine. 362 | 363 | triangle_ls_lp([],_,[],_,Out) -> Out; 364 | triangle_ls_lp(LSs1, P1, [], P2, Out) -> 365 | SLSs = tri_ls_ysort(line_to_linespans(P2,P1,1)), 366 | N2 = length(SLSs), 367 | N1 = length(LSs1), 368 | if 369 | N1 > N2 -> 370 | [_|ILSs] = LSs1, 371 | triangle_ls_lp(ILSs, SLSs, Out); 372 | N2 > N1 -> 373 | [_|ILSs] = SLSs, 374 | triangle_ls_lp(LSs1, ILSs, Out); 375 | true -> 376 | triangle_ls_lp(LSs1, SLSs, Out) 377 | end; 378 | triangle_ls_lp([], P1, LSs2, P2, Out) -> 379 | SLSs = tri_ls_ysort(line_to_linespans(P1,P2,1)), 380 | N1 = length(SLSs), 381 | N2 = length(LSs2), 382 | if 383 | N1 > N2 -> 384 | [_|ILSs] = SLSs, 385 | triangle_ls_lp(ILSs, LSs2, Out); 386 | N2 > N1 -> 387 | [_|ILSs] = LSs2, 388 | triangle_ls_lp(SLSs, ILSs, Out); 389 | true -> 390 | triangle_ls_lp(SLSs, LSs2, Out) 391 | end; 392 | triangle_ls_lp([LS1|LSs1],P1,[LS2|LSs2],P2, Out) -> 393 | {Y, Xl1, Xr1,_Ca1} = LS1, 394 | {_, Xl2, Xr2,_Ca2} = LS2, 395 | Xr = lists:max([Xl1,Xr1,Xl2,Xr2]), 396 | Xl = lists:min([Xl1,Xr1,Xl2,Xr2]), 397 | triangle_ls_lp(LSs1,P1,LSs2,P2,[{Y,Xl,Xr}|Out]). 398 | 399 | triangle_ls_lp([],[],Out) -> Out; 400 | triangle_ls_lp([],_,Out) -> Out; 401 | triangle_ls_lp(_,[],Out) -> Out; 402 | triangle_ls_lp([LS1|LSs1], [LS2|LSs2], Out) -> 403 | {Y, Xl1, Xr1, _Ca1} = LS1, 404 | {_, Xl2, Xr2, _Ca2} = LS2, 405 | Xr = lists:max([Xl1,Xr1,Xl2,Xr2]), 406 | Xl = lists:min([Xl1,Xr1,Xl2,Xr2]), 407 | triangle_ls_lp(LSs1,LSs2,[{Y,Xl,Xr}|Out]). 408 | 409 | tri_pt_ysort(Pts) -> 410 | % {X,Y} 411 | lists:sort( 412 | fun ({_,Y1},{_,Y2}) -> 413 | if Y1 > Y2 -> false; true -> true end 414 | end, Pts). 415 | 416 | tri_ls_ysort(LSs) -> 417 | % {Y, Xl, Xr, Ca} 418 | lists:sort( 419 | fun ({Y1,_,_,_},{Y2,_,_,_}) -> 420 | if Y1 > Y2 -> false; true -> true end 421 | end, LSs). 422 | 423 | % polygon_ls 424 | % In: 425 | % Pts :: [{X,Y}] 426 | % Out: 427 | % LSs :: [{Y,Xl,Xr}] 428 | % Purpose: 429 | % Make polygon line spans 430 | % Algorithm: 431 | % 1. Find the left most (lm) point 432 | % 2. Find the two points adjacent to that point 433 | % The tripplet will make a triangle 434 | % 3. Ensure no points lies within the triangle 435 | % 4a.No points within triangle, 436 | % make triangle, 437 | % remove lm point 438 | % 1. 439 | % 4b.point(s) within triangle, 440 | % 441 | 442 | 443 | polygon_ls(Pts) -> 444 | % Make triangles 445 | Tris = polygon_tri(Pts), 446 | % interval triangles 447 | lists:flatten(polygon_tri_ls(Tris, [])). 448 | 449 | polygon_tri_ls([], Out) -> Out; 450 | polygon_tri_ls([{P1,P2,P3}|Tris], Out) -> 451 | polygon_tri_ls(Tris, [triangle_ls(P1,P2,P3)|Out]). 452 | 453 | polygon_tri(Pts) -> 454 | polygon_tri(polygon_lm_pt(Pts), []). 455 | 456 | 457 | polygon_tri([P1,P2,P3],Tris) -> [{P1,P2,P3}|Tris]; 458 | polygon_tri([P2,P1,P3|Pts], Tris) -> 459 | case polygon_tri_test(P1,P2,P3,Pts) of 460 | false -> polygon_tri(polygon_lm_pt([P2,P3|Pts]), [{P1,P2,P3}|Tris]); 461 | [LmPt|Ptsn] -> polygon_tri([P2,P1,LmPt,P3|Ptsn], Tris) 462 | end. 463 | 464 | polygon_tri_test(P1,P2,P3, Pts) -> 465 | polygon_tri_test(P1,P2,P3, Pts, []). 466 | 467 | polygon_tri_test(_,_,_, [], _) -> false; 468 | polygon_tri_test(P1,P2,P3,[Pt|Pts], Ptsr) -> 469 | case point_inside_triangle(Pt, P1,P2,P3) of 470 | false -> polygon_tri_test(P1,P2,P3, Pts, [Pt|Ptsr]); 471 | true -> [Pt|Pts] ++ lists:reverse(Ptsr) 472 | end. 473 | 474 | % polygon_lm_pt 475 | % In: 476 | % Pts :: [{X,Y}] 477 | % Out 478 | % LmPts = [{X0,Y0},{Xmin,Y0},{X1,Y1},...] 479 | % Purpose: 480 | % The order of the list is important 481 | % rotate the elements until Xmin is first 482 | % This is not extremly fast. 483 | 484 | polygon_lm_pt(Pts) -> 485 | Xs = [X||{X,_}<-Pts], 486 | polygon_lm_pt(Pts, lists:min(Xs), []). 487 | 488 | polygon_lm_pt([Pt0,{X,_}=Ptm | Pts], Xmin, Ptsr) when X > Xmin -> 489 | polygon_lm_pt([Ptm|Pts], Xmin, [Pt0|Ptsr]); 490 | polygon_lm_pt(Pts, _, Ptsr) -> 491 | Pts ++ lists:reverse(Ptsr). 492 | 493 | 494 | % return true if P is inside triangle (p1,p2,p3), 495 | % otherwise false. 496 | 497 | points_same_side({P1x,P1y}, {P2x,P2y}, {L1x,L1y}, {L2x,L2y}) -> 498 | ((P1x - L1x)*(L2y - L1y) - (L2x - L1x)*(P1y - L1y) * 499 | (P2x - L1x)*(L2y - L1y) - (L2x - L1x)*(P2y - L1y)) >= 0. 500 | 501 | point_inside_triangle(P, P1, P2, P3) -> 502 | points_same_side(P, P1, P2, P3) and 503 | points_same_side(P, P2, P1, P3) and 504 | points_same_side(P, P3, P1, P2). 505 | 506 | %% [{Y, Xl, Xr}] -> #{Y := [{Xl,Xr}]} 507 | %% Reorganize linspans to a map with Y as key. 508 | 509 | linespans_to_map(Ls) -> 510 | linespans_to_map(Ls,#{}). 511 | linespans_to_map([{Y,Xl,Xr,C}|Ls], M) -> 512 | case M of 513 | #{Y := Spans} -> linespans_to_map(Ls, M#{Y := [{Xl,Xr,C}|Spans]}); 514 | _ -> linespans_to_map(Ls, M#{Y => [{Xl,Xr,C}]}) 515 | end; 516 | linespans_to_map([], M) -> 517 | M. 518 | 519 | 520 | %% line_to_linespans 521 | %% Anti-aliased thick line 522 | %% Do it CPS style 523 | %% In: 524 | %% P1 :: point() 525 | %% P2 :: point() 526 | %% Out: 527 | %% [{Y,Xl,Xr}] 528 | %% 529 | line_to_linespans({X0,Y0},{X1,Y1},Wd) -> 530 | Dx = abs(X1-X0), 531 | Dy = abs(Y1-Y0), 532 | Sx = if X0 < X1 -> 1; true -> -1 end, 533 | Sy = if Y0 < Y1 -> 1; true -> -1 end, 534 | E0 = Dx - Dy, 535 | Ed = if Dx + Dy =:= 0 -> 1; true -> math:sqrt(Dx*Dx + Dy*Dy) end, 536 | line_to_ls(X0,Y0,X1,Y1,Dx,Dy,Sx,Sy,E0,Ed,(Wd+1)/2,[]). 537 | 538 | line_to_ls(X0,Y0,X1,Y1,Dx,Dy,Sx,Sy,E,Ed,Wd,Ls0) -> 539 | C = max(0, 255*(abs(E - Dx+Dy)/Ed - Wd + 1)), 540 | Ls1 = [{Y0,X0,X0,C}|Ls0], 541 | line_to_ls_sx(X0,Y0,X1,Y1,Dx,Dy,Sx,Sy,E,Ed,Wd,Ls1,E). 542 | 543 | line_to_ls_sx(X0,Y0,X1,Y1,Dx,Dy,Sx,Sy,E,Ed,Wd,Ls,E2) when 2*E2 > -Dx -> 544 | line_to_ls_sx_do(X0,Y0,X1,Y1,Dx,Dy,Sx,Sy,E,Ed,Wd,Ls,E2+Dy,Y0); 545 | line_to_ls_sx(X0,Y0,X1,Y1,Dx,Dy,Sx,Sy,E,Ed,Wd,Ls,E2) -> 546 | line_to_ls_sy(X0,Y0,X1,Y1,Dx,Dy,Sx,Sy,E,Ed,Wd,Ls,E2,X0). 547 | 548 | line_to_ls_sx_do(X0,Y0,X1,Y1,Dx,Dy,Sx,Sy,E,Ed,Wd,Ls0,E2,Y) when E2 < Ed*Wd andalso 549 | (Y1 =/= Y orelse Dx > Dy) -> 550 | Y2 = Y + Sy, 551 | C = max(0,255*(abs(E2)/Ed-Wd+1)), 552 | Ls = [{Y2,X0,X0,C}|Ls0], 553 | line_to_ls_sx_do(X0,Y0,X1,Y1,Dx,Dy,Sx,Sy,E,Ed,Wd,Ls,E2+Dx,Y2); 554 | line_to_ls_sx_do(X0,_Y0,X1,_Y1,_Dx,_Dy,_Sx,_Sy,_E,_Ed,_Wd,Ls,_E2,_Y) when X0 =:= X1 -> 555 | Ls; 556 | line_to_ls_sx_do(X0,Y0,X1,Y1,Dx,Dy,Sx,Sy,E,Ed,Wd,Ls,_E2,_Y) -> 557 | line_to_ls_sy(X0+Sx,Y0,X1,Y1,Dx,Dy,Sx,Sy,E-Dy,Ed,Wd,Ls,E,X0). 558 | 559 | line_to_ls_sy(X0,Y0,X1,Y1,Dx,Dy,Sx,Sy,E,Ed,Wd,Ls0,E2,X) when 2*E2 =< Dy -> 560 | line_to_ls_sy_do(X0,Y0,X1,Y1,Dx,Dy,Sx,Sy,E,Ed,Wd,Ls0,Dx-E2,X); 561 | line_to_ls_sy(X0,Y0,X1,Y1,Dx,Dy,Sx,Sy,E,Ed,Wd,Ls0,_E2,_X) -> 562 | line_to_ls(X0,Y0,X1,Y1,Dx,Dy,Sx,Sy,E,Ed,Wd,Ls0). 563 | 564 | line_to_ls_sy_do(X0,Y0,X1,Y1,Dx,Dy,Sx,Sy,E,Ed,Wd,Ls0,E2,X) when E2 < Ed*Wd andalso 565 | (X1 =/= X orelse Dx < Dy) -> 566 | X2 = X + Sx, 567 | C = max(0,255*(abs(E2)/Ed-Wd+1)), 568 | Ls = [{Y0,X2,X2,C}|Ls0], 569 | line_to_ls_sy_do(X0,Y0,X1,Y1,Dx,Dy,Sx,Sy,E,Ed,Wd,Ls,E2+Dy,X2); 570 | line_to_ls_sy_do(_X0,Y0,_X1,Y1,_Dx,_Dy,_Sx,_Sy,_E,_Ed,_Wd,Ls,_E2,_X) when Y0 =:= Y1 -> 571 | Ls; 572 | line_to_ls_sy_do(X0,Y0,X1,Y1,Dx,Dy,Sx,Sy,E,Ed,Wd,Ls0,_E2,_X) -> 573 | line_to_ls(X0,Y0+Sy,X1,Y1,Dx,Dy,Sx,Sy,E+Dx,Ed,Wd,Ls0). 574 | 575 | % Text 576 | 577 | text_horizontal_ls(Point, Font, Chars) -> 578 | {_Fw,Fh} = egd_font:size(Font), 579 | text_intervals(Point, Fh, Font, Chars, []). 580 | 581 | % This is stupid. The starting point is the top left (Ptl) but the font 582 | % offsets is relative to the bottom right origin, 583 | % {Xtl,Ytl} ------------------------- 584 | % | | 585 | % | Glyph BoundingBox | 586 | % | -------- | 587 | % | |Bitmap| Gh | 588 | % FH |-Gx0-|Data | | 589 | % | -------- | 590 | % | | | 591 | % | Gy0 | 592 | % | | | 593 | % Glyph (0,0)------------------------- Gxm (Glyph X move) 594 | % FW 595 | % Therefore, we need Yo, which is Yo = FH - Gy0 - Gh, 596 | % Font height minus Glyph Y offset minus Glyph bitmap data boundingbox 597 | % height. 598 | 599 | text_intervals( _, _, _, [], Out) -> lists:flatten(Out); 600 | text_intervals({Xtl,Ytl}, Fh, Font, [Code|Chars], Out) -> 601 | {{_Gw, Gh, Gx0, Gy0, Gxm}, LSs} = egd_font:glyph(Font, Code), 602 | % Set offset points from translation matrix to point in TeInVe. 603 | Yo = Fh - Gh + Gy0, 604 | GLSs = text_intervals_vertical({Xtl+Gx0,Ytl+Yo},LSs, []), 605 | text_intervals({Xtl+Gxm,Ytl}, Fh, Font, Chars, [GLSs|Out]). 606 | 607 | text_intervals_vertical( _, [], Out) -> Out; 608 | text_intervals_vertical({Xtl, Ytl}, [LS|LSs], Out) -> 609 | H = lists:foldl( 610 | fun ({Xl,Xr}, RLSs) -> 611 | [{Ytl, Xl + Xtl, Xr + Xtl}|RLSs] 612 | end, [], LS), 613 | text_intervals_vertical({Xtl, Ytl+1}, LSs, [H|Out]). 614 | 615 | 616 | %%% E. PostScript implementation 617 | 618 | eps(#image{ objects = Os, width = W, height = H}) -> 619 | list_to_binary([eps_header(W,H),eps_objects(H,Os),eps_footer()]). 620 | 621 | eps_objects(H,Os) -> eps_objects(H,Os, []). 622 | eps_objects(_,[], Out) -> lists:flatten(Out); 623 | eps_objects(H,[O|Os], Out) -> eps_objects(H,Os, [eps_object(H,O)|Out]). 624 | 625 | eps_object(H,#image_object{ type = text_horizontal, internals = {_Font,Text}, points = [{X,Y}], color={R,G,B,_}}) -> 626 | s("/Times-Roman findfont\n14 scalefont\nsetfont\n~.4f ~.4f ~.4f setrgbcolor\nnewpath\n~p ~p moveto\n(~s) show~n", 627 | [R,G,B,X,H-(Y + 10), Text]); 628 | eps_object(H,#image_object{ type = filled_ellipse, points = [{X1,Y1p},{X2,Y2p}], color={R,G,B,_}}) -> 629 | Y1 = H - Y1p, 630 | Y2 = H - Y2p, 631 | Xr = trunc((X2-X1)/2), 632 | Yr = trunc((Y2-Y1)/2), 633 | Cx = X1 + Xr, 634 | Cy = Y1 + Yr, 635 | s("~.4f ~.4f ~.4f setrgbcolor\nnewpath\n~p ~p ~p ~p 0 360 ellipse fill\n", 636 | [R,G,B,Cx,Cy,Xr,Yr]); 637 | eps_object(H,#image_object{ type = arc, points = [P0, P1], internals = D, color={R,G,B,_}}) -> 638 | Es = egd_primitives:arc_to_edges(P0, P1, D), 639 | [s("~.4f ~.4f ~.4f setrgbcolor\n", [R,G,B])|lists:foldl(fun 640 | ({{X1,Y1},{X2,Y2}}, Eps) -> 641 | [s("newpath\n~p ~p moveto\n~p ~p lineto\n1 setlinewidth\nstroke\n", [X1,H-Y1,X2,H-Y2])|Eps] 642 | end, [], Es)]; 643 | 644 | eps_object(H,#image_object{ type = line, points = [{X1,Y1}, {X2,Y2}], color={R,G,B,_}}) -> 645 | s("~.4f ~.4f ~.4f setrgbcolor\nnewpath\n~p ~p moveto\n~p ~p lineto\n1 setlinewidth\nstroke\n", 646 | [R,G,B,X1,H-Y1,X2,H-Y2]); 647 | eps_object(H,#image_object{ type = rectangle, points = [{X1,Y1}, {X2,Y2}], color={R,G,B,_}}) -> 648 | s("~.4f ~.4f ~.4f setrgbcolor\nnewpath\n~p ~p moveto\n~p ~p lineto\n~p ~p lineto\n~p ~p lineto\n~p ~p lineto\n1 setlinewidth\nstroke\n", 649 | [R,G,B,X1,H-Y1,X2,H-Y1,X2,H-Y2,X1,H-Y2,X1,H-Y1]); 650 | eps_object(H,#image_object{ type = filled_rectangle, points = [{X1,Y1}, {X2,Y2}], color={R,G,B,_}}) -> 651 | s("~.4f ~.4f ~.4f setrgbcolor\nnewpath\n~p ~p moveto\n~p ~p lineto\n~p ~p lineto\n~p ~p lineto\n~p ~p lineto\nclosepath\nfill\n", 652 | [R,G,B,X1,H-Y1,X2,H-Y1,X2,H-Y2,X1,H-Y2,X1,H-Y1]); 653 | eps_object(_,_) -> "". 654 | 655 | s(Format, Terms) -> lists:flatten(io_lib:format(Format, Terms)). 656 | 657 | eps_header(W,H) -> 658 | s("%!PS-Adobe-3.0 EPSF-3.0\n%%Creator: Created by egd\n%%BoundingBox: 0 0 ~p ~p\n%%LanguageLevel: 2\n%%Pages: 1\n%%DocumentData: Clean7Bit\n",[W,H]) ++ 659 | "%%BeginProlog\n/ellipse {7 dict begin\n/endangle exch def\n/startangle exch def\n/yradius exch def\n/xradius exch def\n/yC exch def\n/xC exch def\n" 660 | "/savematrix matrix currentmatrix def\nxC yC translate\nxradius yradius scale\n0 0 1 startangle endangle arc\nsavematrix setmatrix\nend\n} def\n" 661 | "%%EndProlog\n". 662 | 663 | eps_footer() -> 664 | "%%EOF\n". 665 | -------------------------------------------------------------------------------- /test/egd.spec: -------------------------------------------------------------------------------- 1 | {suites,".",all}. 2 | -------------------------------------------------------------------------------- /test/egd_SUITE.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% %CopyrightBegin% 3 | %% 4 | %% Copyright Ericsson AB 2007-2016. All Rights Reserved. 5 | %% 6 | %% Licensed under the Apache License, Version 2.0 (the "License"); 7 | %% you may not use this file except in compliance with the License. 8 | %% You may obtain a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, software 13 | %% distributed under the License is distributed on an "AS IS" BASIS, 14 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | %% See the License for the specific language governing permissions and 16 | %% limitations under the License. 17 | %% 18 | %% %CopyrightEnd% 19 | %% 20 | 21 | -module(egd_SUITE). 22 | -include_lib("common_test/include/ct.hrl"). 23 | 24 | %% Test server specific exports 25 | -export([all/0, suite/0]). 26 | -export([init_per_suite/1, end_per_suite/1]). 27 | -export([init_per_testcase/2, end_per_testcase/2]). 28 | 29 | %% Test cases 30 | -export([image_create_and_destroy/1, 31 | image_shape/1, 32 | image_primitives/1, 33 | image_colors/1, 34 | image_font/1, 35 | image_fans/1, 36 | image_png_compliant/1]). 37 | 38 | suite() -> 39 | [{timetrap, {minutes, 1}}]. 40 | 41 | all() -> 42 | [image_create_and_destroy, image_shape, 43 | image_primitives, image_colors, image_font, 44 | image_fans, 45 | image_png_compliant]. 46 | 47 | 48 | init_per_suite(Config) when is_list(Config) -> 49 | rand:seed(exsplus), 50 | Config. 51 | 52 | end_per_suite(Config) when is_list(Config) -> 53 | Config. 54 | 55 | init_per_testcase(_Case, Config) -> 56 | [{max_size, 800}|Config]. 57 | 58 | end_per_testcase(_Case, _Config) -> 59 | ok. 60 | 61 | %%---------------------------------------------------------------------- 62 | %% Tests 63 | %%---------------------------------------------------------------------- 64 | 65 | %% Image creation and destroy test. 66 | image_create_and_destroy(Config) when is_list(Config) -> 67 | {W,H} = get_size(proplists:get_value(max_size, Config)), 68 | Image = egd:create(W, H), 69 | ok = egd:destroy(Image), 70 | ok. 71 | 72 | %% Image color test. 73 | image_colors(Config) when is_list(Config) -> 74 | {W,H} = get_size(proplists:get_value(max_size, Config)), 75 | Dir = proplists:get_value(priv_dir, Config), 76 | Image = egd:create(W, H), 77 | put(image_size, {W,H}), 78 | 79 | RGB = get_rgb(), 80 | Black = egd:color({0,0,0}), 81 | Red = egd:color({255,0,0}), 82 | Green = egd:color({0,255,0}), 83 | Blue = egd:color({0,0,255}), 84 | Random = egd:color(Image, RGB), 85 | 86 | ok = egd:line(Image, get_point(), get_point(), Random), 87 | ok = egd:line(Image, get_point(), get_point(), Red), 88 | ok = egd:line(Image, get_point(), get_point(), Green), 89 | ok = egd:line(Image, get_point(), get_point(), Black), 90 | ok = egd:line(Image, get_point(), get_point(), Blue), 91 | 92 | HtmlDefaultNames = [black,silver,gray,white,maroon,red, 93 | purple,fuchia,green,lime,olive,yellow,navy,blue,teal, 94 | aqua], 95 | 96 | lists:foreach(fun (ColorName) -> 97 | Color = egd:color(ColorName), 98 | ok = egd:line(Image, get_point(), get_point(), Color) 99 | end, HtmlDefaultNames), 100 | 101 | Png1 = <<_/binary>> = egd:render(Image,png,[{render_engine, alpha}]), 102 | File1 = filename:join(Dir,"image_colors_alpha.png"), 103 | ok = egd:save(Png1,File1), 104 | ct:log("

Image alpha:

~n", [File1]), 105 | Png2 = <<_/binary>> = egd:render(Image,png,[{render_engine, opaque}]), 106 | File2 = filename:join(Dir,"image_colors_opaque.png"), 107 | ok = egd:save(Png2,File2), 108 | ct:log("

Image opaque:

~n", [File2]), 109 | 110 | ok = egd:destroy(Image), 111 | erase(image_size), 112 | ok. 113 | 114 | %% Image shape API test. 115 | image_shape(Config) when is_list(Config) -> 116 | {W,H} = get_size(proplists:get_value(max_size, Config)), 117 | Dir = proplists:get_value(priv_dir, Config), 118 | put(image_size, {W,H}), 119 | Im = egd:create(W, H), 120 | 121 | Fgc = egd:color({255,0,0}), 122 | 123 | ok = egd:line(Im, get_point(), get_point(), Fgc), 124 | ok = egd:rectangle(Im, get_point(), get_point(), Fgc), 125 | ok = egd:filledEllipse(Im, get_point(), get_point(), Fgc), 126 | ok = egd:arc(Im, get_point(), get_point(), Fgc), 127 | ok = egd:arc(Im, get_point(), get_point(), 100, Fgc), 128 | 129 | Pt1 = get_point(), 130 | Pt2 = get_point(), 131 | 132 | ok = egd:filledRectangle(Im, Pt1, Pt2, Fgc), 133 | 134 | Bitmap = egd:render(Im, raw_bitmap), 135 | 136 | ok = bitmap_point_has_color(Bitmap, {W,H}, Pt2, Fgc), 137 | ok = bitmap_point_has_color(Bitmap, {W,H}, Pt1, Fgc), 138 | 139 | Bin = <<_/binary>> = egd:render(Im, raw_bitmap, [{render_engine, alpha}]), 140 | Png = egd_png:binary(W,H,Bin), 141 | File = filename:join(Dir,"image_shape.png"), 142 | ok = egd:save(Png,File), 143 | ct:log("

Image:

~n", [File]), 144 | 145 | ok = egd:destroy(Im), 146 | 147 | erase(image_size), 148 | ok. 149 | 150 | %% Image shape API test. 151 | image_primitives(Config) when is_list(Config) -> 152 | {W,H} = get_size(proplists:get_value(max_size, Config)), 153 | Dir = proplists:get_value(priv_dir, Config), 154 | put(image_size, {W,H}), 155 | 156 | Im0 = egd_primitives:create(W, H), 157 | Fgc = egd:color({25,25,255}), 158 | Bgc = egd:color({0,250,25}), 159 | 160 | Im1 = lists:foldl(fun ({Function, Arguments}, Im) -> 161 | erlang:apply(egd_primitives, Function, [Im|Arguments]) 162 | end, Im0, 163 | [{Fs, [get_point(), get_point(), Bgc]} || Fs <- [line, rectangle, filledEllipse, arc]] ++ 164 | [{pixel, [get_point(), Bgc]}, 165 | {filledTriangle, [get_point(), get_point(), get_point(), Bgc]}]), 166 | 167 | Pt1 = get_point(), 168 | Pt2 = get_point(), 169 | 170 | Im2 = egd_primitives:filledRectangle(Im1, Pt1, Pt2, Fgc), 171 | 172 | Bitmap = egd_render:binary(Im2, opaque), 173 | 174 | ok = bitmap_point_has_color(Bitmap, {W,H}, Pt2, Fgc), 175 | ok = bitmap_point_has_color(Bitmap, {W,H}, Pt1, Fgc), 176 | 177 | Bin = <<_/binary>> = egd_render:binary(Im2, alpha), 178 | Png = egd_png:binary(W,H,Bin), 179 | File = filename:join(Dir,"image_primitives.png"), 180 | ok = egd:save(Png,File), 181 | ct:log("

Image:

~n", [File]), 182 | 183 | erase(image_size), 184 | ok. 185 | 186 | %% Image font test. 187 | image_font(Config) when is_list(Config) -> 188 | {W,H} = get_size(proplists:get_value(max_size, Config)), 189 | Dir = proplists:get_value(priv_dir, Config), 190 | put(image_size, {W,H}), 191 | Im = egd:create(W, H), 192 | Fgc = egd:color({0,130,0}), 193 | 194 | Filename = filename:join([code:priv_dir(egd),"fonts","6x11_latin1.wingsfont"]), 195 | Font = egd_font:load(Filename), 196 | 197 | % simple text 198 | ok = egd:text(Im, get_point(), Font, "Hello World", Fgc), 199 | <<_/binary>> = egd:render(Im, png), 200 | 201 | GlyphStr1 = " !\"#$%&'()*+,-./", % Codes 32 -> 47 202 | NumericStr = "0123456789", % Codes 48 -> 57 203 | GlyphStr2 = ":;<=>?@", % Codes 58 -> 64 204 | AlphaBigStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZ", % Codes 65 -> 90 205 | GlyphStr3 = "[\\]^_`", % Codes 91 -> 96 206 | AlphaSmStr = "abcdefghijklmnopqrstuvwxyz", % Codes 97 -> 122 207 | GlyphStr4 = "{|}~", % Codes 123 -> 126 208 | 209 | ok = egd:text(Im, get_point(), Font, GlyphStr1, Fgc), 210 | Png1 = <<_/binary>> = egd:render(Im, png), 211 | File1 = filename:join(Dir,"text1.png"), 212 | ok = egd:save(Png1,File1), 213 | ct:log("

Image:

~n", [File1]), 214 | 215 | ok = egd:text(Im, get_point(), Font, NumericStr, Fgc), 216 | Png2 = <<_/binary>> = egd:render(Im, png), 217 | File2 = filename:join(Dir,"text2.png"), 218 | ok = egd:save(Png2,File2), 219 | ct:log("

Image:

~n", [File2]), 220 | 221 | ok = egd:text(Im, get_point(), Font, GlyphStr2, Fgc), 222 | Png3 = <<_/binary>> = egd:render(Im, png), 223 | File3 = filename:join(Dir,"text3.png"), 224 | ok = egd:save(Png3,File3), 225 | ct:log("

Image:

~n", [File3]), 226 | 227 | ok = egd:text(Im, get_point(), Font, AlphaBigStr, Fgc), 228 | Png4 = <<_/binary>> = egd:render(Im, png), 229 | File4 = filename:join(Dir,"text4.png"), 230 | ok = egd:save(Png4,File4), 231 | ct:log("

Image:

~n", [File4]), 232 | 233 | ok = egd:text(Im, get_point(), Font, GlyphStr3, Fgc), 234 | Png5 = <<_/binary>> = egd:render(Im, png), 235 | File5 = filename:join(Dir,"text5.png"), 236 | ok = egd:save(Png5,File5), 237 | ct:log("

Image:

~n", [File5]), 238 | 239 | ok = egd:text(Im, get_point(), Font, AlphaSmStr, Fgc), 240 | Png6 = <<_/binary>> = egd:render(Im, png), 241 | File6 = filename:join(Dir,"text6.png"), 242 | ok = egd:save(Png6,File6), 243 | ct:log("

Image:

~n", [File6]), 244 | 245 | ok = egd:text(Im, get_point(), Font, GlyphStr4, Fgc), 246 | Png7 = <<_/binary>> = egd:render(Im, png), 247 | File7 = filename:join(Dir,"text7.png"), 248 | ok = egd:save(Png7,File7), 249 | ct:log("

Image:

~n", [File7]), 250 | 251 | ok = egd:destroy(Im), 252 | erase(image_size), 253 | ok. 254 | 255 | %% Image png compliant test. 256 | image_png_compliant(Config) when is_list(Config) -> 257 | {W,H} = get_size(proplists:get_value(max_size, Config)), 258 | put(image_size, {W,H}), 259 | Im = egd:create(W, H), 260 | Fgc = egd:color({0,0,0}), 261 | ok = egd:filledRectangle(Im, get_point(), get_point(), Fgc), 262 | 263 | Bin = egd:render(Im, png), 264 | true = binary_is_png_compliant(Bin), 265 | 266 | ok = egd:destroy(Im), 267 | erase(image_size), 268 | ok. 269 | 270 | image_fans(Config) when is_list(Config) -> 271 | W = 1024, 272 | H = 800, 273 | Dir = proplists:get_value(priv_dir, Config), 274 | 275 | Fun = fun({F,Args},Im) -> 276 | erlang:apply(egd_primitives,F,[Im|Args]) 277 | end, 278 | 279 | %% fan1 280 | Ops1 = gen_vertical_fan(1,{0,400},egd:color(red),1024,800,-15), 281 | Ops2 = gen_horizontal_fan(1,{512,800},egd:color(green),1024,0,-15), 282 | 283 | Im0 = egd_primitives:create(W,H), 284 | Im1 = lists:foldl(Fun, Im0, Ops1 ++ Ops2), 285 | Bin1 = egd_render:binary(Im1, opaque), 286 | Png1 = egd_png:binary(W,H,Bin1), 287 | 288 | File1 = filename:join(Dir,"fan1_opaque.png"), 289 | ok = egd:save(Png1,File1), 290 | ct:log("

Image opaque width 1:

~n", [File1]), 291 | 292 | Bin2 = egd_render:binary(Im1, alpha), 293 | Png2 = egd_png:binary(W,H,Bin2), 294 | 295 | File2 = filename:join(Dir,"fan1_alpha.png"), 296 | ok = egd:save(Png2,File2), 297 | ct:log("

Image alpha width 1:

~n", [File2]), 298 | 299 | 300 | %% fan2 301 | Ops3 = gen_vertical_fan(7,{0,400},egd:color(red),1024,800,-15), 302 | Ops4 = gen_horizontal_fan(7,{512,800},egd:color(green),1024,0,-15), 303 | 304 | Im2 = lists:foldl(Fun, Im0, Ops3 ++ Ops4), 305 | Bin3 = egd_render:binary(Im2, opaque), 306 | Png3 = egd_png:binary(W,H,Bin3), 307 | 308 | File3 = filename:join(Dir,"fan2_opaque.png"), 309 | ok = egd:save(Png3,File3), 310 | ct:log("

Image opaque width 7:

~n", [File3]), 311 | 312 | Bin4 = egd_render:binary(Im2, alpha), 313 | Png4 = egd_png:binary(W,H,Bin4), 314 | 315 | File4 = filename:join(Dir,"fan2_alpha.png"), 316 | ok = egd:save(Png4,File4), 317 | ct:log("

Image alpha width 7:

~n", [File4]), 318 | ok. 319 | 320 | gen_vertical_fan(Wd,Pt,C,X,Y,Step) when Y > 0 -> 321 | [{line,[Pt,{X,Y},Wd,C]}|gen_vertical_fan(Wd,Pt,C,X,Y + Step,Step)]; 322 | gen_vertical_fan(_,_,_,_,_,_) -> []. 323 | 324 | gen_horizontal_fan(Wd,Pt,C,X,Y,Step) when X > 0 -> 325 | [{line,[Pt,{X,Y},Wd,C]}|gen_horizontal_fan(Wd,Pt,C,X + Step,Y,Step)]; 326 | gen_horizontal_fan(_,_,_,_,_,_) -> []. 327 | 328 | 329 | %%---------------------------------------------------------------------- 330 | %% Auxiliary tests 331 | %%---------------------------------------------------------------------- 332 | 333 | bitmap_point_has_color(Bitmap, {W,_}, {X,Y}, C) -> 334 | {CR,CG,CB,_} = egd_primitives:rgb_float2byte(C), 335 | N = W*Y*3 + X*3, 336 | << _:N/binary, R,G,B, _/binary>> = Bitmap, 337 | case {R,G,B} of 338 | {CR,CG,CB} -> ok; 339 | Other -> 340 | io:format("bitmap_point_has_color: error color was ~p, should be ~p~n", [Other, {CR,CG,CB}]), 341 | {error, {Other,{CR,CG,CB}}} 342 | end. 343 | 344 | binary_is_png_compliant(PngBin) -> 345 | {Bin, _} = split_binary(PngBin, 10), 346 | List = binary_to_list(Bin), 347 | case lists:sublist(List, 2,3) of 348 | "PNG" -> true; 349 | Other -> 350 | io:format("img -> ~p~n", [Other]), 351 | false 352 | end. 353 | 354 | %%---------------------------------------------------------------------- 355 | %% Auxiliary 356 | %%---------------------------------------------------------------------- 357 | 358 | 359 | get_rgb() -> 360 | R = random(255), 361 | G = random(255), 362 | B = random(255), 363 | {R,G,B}. 364 | 365 | get_angle() -> 366 | random(359). 367 | 368 | get_point() -> 369 | get_point(get(image_size)). 370 | get_point({W,H}) -> 371 | X = random(W - 1), 372 | Y = random(H - 1), 373 | {X,Y}. 374 | 375 | get_size(Max) -> 376 | W = trunc(random(Max/2) + Max/2 + 1), 377 | H = trunc(random(Max/2) + Max/2 + 1), 378 | io:format("Image size will be ~p x ~p~n", [W,H]), 379 | {W,H}. 380 | 381 | get_points(N) -> 382 | get_points(N, []). 383 | get_points(0, Out) -> 384 | Out; 385 | get_points(N, Out) -> 386 | get_points(N - 1, [get_point() | Out]). 387 | 388 | random(N) -> trunc(rand:uniform(trunc(N + 1)) - 1). 389 | --------------------------------------------------------------------------------