File: fh.py 1 #!/usr/bin/python 2 3 # The MIT License (MIT) 4 # 5 # Copyright (c) 2026 pacman64 6 # 7 # Permission is hereby granted, free of charge, to any person obtaining a copy 8 # of this software and associated documentation files (the "Software"), to deal 9 # in the Software without restriction, including without limitation the rights 10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 # copies of the Software, and to permit persons to whom the Software is 12 # furnished to do so, subject to the following conditions: 13 # 14 # The above copyright notice and this permission notice shall be included in 15 # all copies or substantial portions of the Software. 16 # 17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 # SOFTWARE. 24 25 # mpl-colormaps by Nathaniel Smith & Stefan van der Walt 26 # 27 # To the extent possible under law, the persons who associated CC0 with 28 # mpl-colormaps have waived all copyright and related or neighboring rights 29 # to mpl-colormaps. 30 # 31 # You should have received a copy of the CC0 legalcode along with this 32 # work. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>. 33 34 # Creative Commons Legal Code 35 # 36 # CC0 1.0 Universal 37 # 38 # CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 39 # LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 40 # ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 41 # INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 42 # REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 43 # PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 44 # THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 45 # HEREUNDER. 46 # 47 # Statement of Purpose 48 # 49 # The laws of most jurisdictions throughout the world automatically confer 50 # exclusive Copyright and Related Rights (defined below) upon the creator 51 # and subsequent owner(s) (each and all, an "owner") of an original work of 52 # authorship and/or a database (each, a "Work"). 53 # 54 # Certain owners wish to permanently relinquish those rights to a Work for 55 # the purpose of contributing to a commons of creative, cultural and 56 # scientific works ("Commons") that the public can reliably and without fear 57 # of later claims of infringement build upon, modify, incorporate in other 58 # works, reuse and redistribute as freely as possible in any form whatsoever 59 # and for any purposes, including without limitation commercial purposes. 60 # These owners may contribute to the Commons to promote the ideal of a free 61 # culture and the further production of creative, cultural and scientific 62 # works, or to gain reputation or greater distribution for their Work in 63 # part through the use and efforts of others. 64 # 65 # For these and/or other purposes and motivations, and without any 66 # expectation of additional consideration or compensation, the person 67 # associating CC0 with a Work (the "Affirmer"), to the extent that he or she 68 # is an owner of Copyright and Related Rights in the Work, voluntarily 69 # elects to apply CC0 to the Work and publicly distribute the Work under its 70 # terms, with knowledge of his or her Copyright and Related Rights in the 71 # Work and the meaning and intended legal effect of CC0 on those rights. 72 # 73 # 1. Copyright and Related Rights. A Work made available under CC0 may be 74 # protected by copyright and related or neighboring rights ("Copyright and 75 # Related Rights"). Copyright and Related Rights include, but are not 76 # limited to, the following: 77 # 78 # i. the right to reproduce, adapt, distribute, perform, display, 79 # communicate, and translate a Work; 80 # ii. moral rights retained by the original author(s) and/or performer(s); 81 # iii. publicity and privacy rights pertaining to a person's image or 82 # likeness depicted in a Work; 83 # iv. rights protecting against unfair competition in regards to a Work, 84 # subject to the limitations in paragraph 4(a), below; 85 # v. rights protecting the extraction, dissemination, use and reuse of data 86 # in a Work; 87 # vi. database rights (such as those arising under Directive 96/9/EC of the 88 # European Parliament and of the Council of 11 March 1996 on the legal 89 # protection of databases, and under any national implementation 90 # thereof, including any amended or successor version of such 91 # directive); and 92 # vii. other similar, equivalent or corresponding rights throughout the 93 # world based on applicable law or treaty, and any national 94 # implementations thereof. 95 # 96 # 2. Waiver. To the greatest extent permitted by, but not in contravention 97 # of, applicable law, Affirmer hereby overtly, fully, permanently, 98 # irrevocably and unconditionally waives, abandons, and surrenders all of 99 # Affirmer's Copyright and Related Rights and associated claims and causes 100 # of action, whether now known or unknown (including existing as well as 101 # future claims and causes of action), in the Work (i) in all territories 102 # worldwide, (ii) for the maximum duration provided by applicable law or 103 # treaty (including future time extensions), (iii) in any current or future 104 # medium and for any number of copies, and (iv) for any purpose whatsoever, 105 # including without limitation commercial, advertising or promotional 106 # purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 107 # member of the public at large and to the detriment of Affirmer's heirs and 108 # successors, fully intending that such Waiver shall not be subject to 109 # revocation, rescission, cancellation, termination, or any other legal or 110 # equitable action to disrupt the quiet enjoyment of the Work by the public 111 # as contemplated by Affirmer's express Statement of Purpose. 112 # 113 # 3. Public License Fallback. Should any part of the Waiver for any reason 114 # be judged legally invalid or ineffective under applicable law, then the 115 # Waiver shall be preserved to the maximum extent permitted taking into 116 # account Affirmer's express Statement of Purpose. In addition, to the 117 # extent the Waiver is so judged Affirmer hereby grants to each affected 118 # person a royalty-free, non transferable, non sublicensable, non exclusive, 119 # irrevocable and unconditional license to exercise Affirmer's Copyright and 120 # Related Rights in the Work (i) in all territories worldwide, (ii) for the 121 # maximum duration provided by applicable law or treaty (including future 122 # time extensions), (iii) in any current or future medium and for any number 123 # of copies, and (iv) for any purpose whatsoever, including without 124 # limitation commercial, advertising or promotional purposes (the 125 # "License"). The License shall be deemed effective as of the date CC0 was 126 # applied by Affirmer to the Work. Should any part of the License for any 127 # reason be judged legally invalid or ineffective under applicable law, such 128 # partial invalidity or ineffectiveness shall not invalidate the remainder 129 # of the License, and in such case Affirmer hereby affirms that he or she 130 # will not (i) exercise any of his or her remaining Copyright and Related 131 # Rights in the Work or (ii) assert any associated claims and causes of 132 # action with respect to the Work, in either case contrary to Affirmer's 133 # express Statement of Purpose. 134 # 135 # 4. Limitations and Disclaimers. 136 # 137 # a. No trademark or patent rights held by Affirmer are waived, abandoned, 138 # surrendered, licensed or otherwise affected by this document. 139 # b. Affirmer offers the Work as-is and makes no representations or 140 # warranties of any kind concerning the Work, express, implied, 141 # statutory or otherwise, including without limitation warranties of 142 # title, merchantability, fitness for a particular purpose, non 143 # infringement, or the absence of latent or other defects, accuracy, or 144 # the present or absence of errors, whether or not discoverable, all to 145 # the greatest extent permissible under applicable law. 146 # c. Affirmer disclaims responsibility for clearing rights of other persons 147 # that may apply to the Work or any use thereof, including without 148 # limitation any person's Copyright and Related Rights in the Work. 149 # Further, Affirmer disclaims responsibility for obtaining any necessary 150 # consents, permissions or other rights required for any use of the 151 # Work. 152 # d. Affirmer understands and acknowledges that Creative Commons is not a 153 # party to this document and has no duty or obligation with respect to 154 # this CC0 or use of the Work. 155 156 157 info = ''' 158 fh [options...] [python expression] 159 160 161 Function Heatmapper emits a picture showing a grayscale-heatmap view of the 162 function f(x, y) implied by the Python math-expression given. Several math 163 functions and constants are available, all their names being lowercase. 164 165 The output is always an uncompressed BMP (bitmap) in RGB (24-bit) mode, and 166 with a resolution of 3840x2160, or 2160x2160 if using option `-square`, or 167 one of its aliases. 168 169 The formula/expression can use floating-point values `x` and `y`, to get 170 the cartesian position each time. All names (functions and values) from 171 the built-in python modules `math` and `random` are also available to the 172 expression run. 173 174 By default `x` and `y` are both sampled regularly from the set 0..1, but 175 those can be changed using the relevant options. 176 177 The formula is expected to result in floating-point values in a finite 178 range, as the final colors will be `scaled` according to the span/range of 179 values the formula gave in the sampled sub-domain of the (x, y) values. 180 181 While many of the examples below save to files, you can just pipe to apps 182 which support the BMP format, which should be the vast majority of picture 183 viewers and media players, such as mpv. 184 185 186 Options 187 188 All options can start with either a single or a double leading dash: 189 190 -h, -help show this help message 191 -hd, -1080p use a 1920x1080 resolution, or 1080x1080 192 -s, -square change output width to be the same as the height 193 -x=[min..max] change span of the sampled x values 194 -xy=[min..max] change spans of the sampled x and y values 195 -y=[min..max] change span of the sampled y values 196 197 198 Examples 199 200 201 fh 'x/(x+y)' > corner-fan-1.png 202 203 fh 'y/(x+y)' > corner-fan-2.png 204 205 fh 'abs(sin(wrap(x, -5, 5)) / wrap(y, 1, 5)**1.4' > floor-lights.png 206 207 fh 'lbeta(wrap(x, -5, 5) + 5.1, wrap(y, 1, 5) + 5.1)' > beta-gradient.png 208 209 fh 'lcm(ceil(wrap(x, 0.01, 199.99)), ceil(wrap(y, 0.01, 199.99)))' > lcm.png 210 211 fh 'abs(wrap(x, -5, 5)) + sqrt(abs(sin(2*wrap(y, -5, 5))))' > bars.png 212 213 fh 'sign(cos(160*tau*x) + sin(90*tau*y))' > 90-deg-square-tiles.png 214 215 fh 'gauss(sin(50*x) * cos(50*9/16*y) + 1)' > light-tiles.png 216 217 # central ripple 218 fh --square 'sinc(0.55 * hypot(wrap(x, -10, 10), wrap(x, -10, 10)))' > cr.png 219 ''' 220 221 222 from sys import argv, stdout 223 224 225 # handle standard help cmd-line options, quitting right away in that case 226 if len(argv) > 1 and argv[1] in ('-h', '--h', '-help', '--help'): 227 print(info.strip()) 228 exit(0) 229 230 231 import math 232 from math import \ 233 acos, acosh, asin, asinh, atan, atan2, atanh, ceil, comb, \ 234 copysign, cos, cosh, degrees, dist, e, erf, erfc, exp, expm1, \ 235 fabs, factorial, floor, fmod, frexp, fsum, gamma, gcd, hypot, inf, \ 236 isclose, isfinite, isinf, isnan, isqrt, lcm, ldexp, lgamma, log, \ 237 log10, log1p, log2, modf, nan, nextafter, perm, pi, pow, prod, \ 238 radians, remainder, sin, sinh, sqrt, tan, tanh, tau, trunc, ulp 239 try: 240 from math import cbrt, exp2 241 except Exception as _: 242 pass 243 244 math_log = log 245 math_sqrt = sqrt 246 247 from random import \ 248 betavariate, choice, choices, expovariate, gammavariate, gauss, \ 249 getrandbits, getstate, lognormvariate, normalvariate, paretovariate, \ 250 randbytes, randint, random, randrange, sample, seed, setstate, \ 251 shuffle, triangular, uniform, vonmisesvariate, weibullvariate 252 253 from struct import pack 254 255 256 # I'm using data straight from the original implementation of Viridis 257 # by Nathaniel Smith & Stefan van der Walt: 258 # https://github.com/BIDS/colormap/blob/master/option_d.py 259 viridis = [ 260 0.26700401, 0.00487433, 0.32941519, 0.26851048, 0.00960483, 0.33542652, 261 0.26994384, 0.01462494, 0.34137895, 0.27130489, 0.01994186, 0.34726862, 262 0.27259384, 0.02556309, 0.35309303, 0.27380934, 0.03149748, 0.35885256, 263 0.27495242, 0.03775181, 0.36454323, 0.27602238, 0.04416723, 0.37016418, 264 0.2770184, 0.05034437, 0.37571452, 0.27794143, 0.05632444, 0.38119074, 265 0.27879067, 0.06214536, 0.38659204, 0.2795655, 0.06783587, 0.39191723, 266 0.28026658, 0.07341724, 0.39716349, 0.28089358, 0.07890703, 0.40232944, 267 0.28144581, 0.0843197, 0.40741404, 0.28192358, 0.08966622, 0.41241521, 268 0.28232739, 0.09495545, 0.41733086, 0.28265633, 0.10019576, 0.42216032, 269 0.28291049, 0.10539345, 0.42690202, 0.28309095, 0.11055307, 0.43155375, 270 0.28319704, 0.11567966, 0.43611482, 0.28322882, 0.12077701, 0.44058404, 271 0.28318684, 0.12584799, 0.44496, 0.283072, 0.13089477, 0.44924127, 272 0.28288389, 0.13592005, 0.45342734, 0.28262297, 0.14092556, 0.45751726, 273 0.28229037, 0.14591233, 0.46150995, 0.28188676, 0.15088147, 0.46540474, 274 0.28141228, 0.15583425, 0.46920128, 0.28086773, 0.16077132, 0.47289909, 275 0.28025468, 0.16569272, 0.47649762, 0.27957399, 0.17059884, 0.47999675, 276 0.27882618, 0.1754902, 0.48339654, 0.27801236, 0.18036684, 0.48669702, 277 0.27713437, 0.18522836, 0.48989831, 0.27619376, 0.19007447, 0.49300074, 278 0.27519116, 0.1949054, 0.49600488, 0.27412802, 0.19972086, 0.49891131, 279 0.27300596, 0.20452049, 0.50172076, 0.27182812, 0.20930306, 0.50443413, 280 0.27059473, 0.21406899, 0.50705243, 0.26930756, 0.21881782, 0.50957678, 281 0.26796846, 0.22354911, 0.5120084, 0.26657984, 0.2282621, 0.5143487, 282 0.2651445, 0.23295593, 0.5165993, 0.2636632, 0.23763078, 0.51876163, 283 0.26213801, 0.24228619, 0.52083736, 0.26057103, 0.2469217, 0.52282822, 284 0.25896451, 0.25153685, 0.52473609, 0.25732244, 0.2561304, 0.52656332, 285 0.25564519, 0.26070284, 0.52831152, 0.25393498, 0.26525384, 0.52998273, 286 0.25219404, 0.26978306, 0.53157905, 0.25042462, 0.27429024, 0.53310261, 287 0.24862899, 0.27877509, 0.53455561, 0.2468114, 0.28323662, 0.53594093, 288 0.24497208, 0.28767547, 0.53726018, 0.24311324, 0.29209154, 0.53851561, 289 0.24123708, 0.29648471, 0.53970946, 0.23934575, 0.30085494, 0.54084398, 290 0.23744138, 0.30520222, 0.5419214, 0.23552606, 0.30952657, 0.54294396, 291 0.23360277, 0.31382773, 0.54391424, 0.2316735, 0.3181058, 0.54483444, 292 0.22973926, 0.32236127, 0.54570633, 0.22780192, 0.32659432, 0.546532, 293 0.2258633, 0.33080515, 0.54731353, 0.22392515, 0.334994, 0.54805291, 294 0.22198915, 0.33916114, 0.54875211, 0.22005691, 0.34330688, 0.54941304, 295 0.21812995, 0.34743154, 0.55003755, 0.21620971, 0.35153548, 0.55062743, 296 0.21429757, 0.35561907, 0.5511844, 0.21239477, 0.35968273, 0.55171011, 297 0.2105031, 0.36372671, 0.55220646, 0.20862342, 0.36775151, 0.55267486, 298 0.20675628, 0.37175775, 0.55311653, 0.20490257, 0.37574589, 0.55353282, 299 0.20306309, 0.37971644, 0.55392505, 0.20123854, 0.38366989, 0.55429441, 300 0.1994295, 0.38760678, 0.55464205, 0.1976365, 0.39152762, 0.55496905, 301 0.19585993, 0.39543297, 0.55527637, 0.19410009, 0.39932336, 0.55556494, 302 0.19235719, 0.40319934, 0.55583559, 0.19063135, 0.40706148, 0.55608907, 303 0.18892259, 0.41091033, 0.55632606, 0.18723083, 0.41474645, 0.55654717, 304 0.18555593, 0.4185704, 0.55675292, 0.18389763, 0.42238275, 0.55694377, 305 0.18225561, 0.42618405, 0.5571201, 0.18062949, 0.42997486, 0.55728221, 306 0.17901879, 0.43375572, 0.55743035, 0.17742298, 0.4375272, 0.55756466, 307 0.17584148, 0.44128981, 0.55768526, 0.17427363, 0.4450441, 0.55779216, 308 0.17271876, 0.4487906, 0.55788532, 0.17117615, 0.4525298, 0.55796464, 309 0.16964573, 0.45626209, 0.55803034, 0.16812641, 0.45998802, 0.55808199, 310 0.1666171, 0.46370813, 0.55811913, 0.16511703, 0.4674229, 0.55814141, 311 0.16362543, 0.47113278, 0.55814842, 0.16214155, 0.47483821, 0.55813967, 312 0.16066467, 0.47853961, 0.55811466, 0.15919413, 0.4822374, 0.5580728, 313 0.15772933, 0.48593197, 0.55801347, 0.15626973, 0.4896237, 0.557936, 314 0.15481488, 0.49331293, 0.55783967, 0.15336445, 0.49700003, 0.55772371, 315 0.1519182, 0.50068529, 0.55758733, 0.15047605, 0.50436904, 0.55742968, 316 0.14903918, 0.50805136, 0.5572505, 0.14760731, 0.51173263, 0.55704861, 317 0.14618026, 0.51541316, 0.55682271, 0.14475863, 0.51909319, 0.55657181, 318 0.14334327, 0.52277292, 0.55629491, 0.14193527, 0.52645254, 0.55599097, 319 0.14053599, 0.53013219, 0.55565893, 0.13914708, 0.53381201, 0.55529773, 320 0.13777048, 0.53749213, 0.55490625, 0.1364085, 0.54117264, 0.55448339, 321 0.13506561, 0.54485335, 0.55402906, 0.13374299, 0.54853458, 0.55354108, 322 0.13244401, 0.55221637, 0.55301828, 0.13117249, 0.55589872, 0.55245948, 323 0.1299327, 0.55958162, 0.55186354, 0.12872938, 0.56326503, 0.55122927, 324 0.12756771, 0.56694891, 0.55055551, 0.12645338, 0.57063316, 0.5498411, 325 0.12539383, 0.57431754, 0.54908564, 0.12439474, 0.57800205, 0.5482874, 326 0.12346281, 0.58168661, 0.54744498, 0.12260562, 0.58537105, 0.54655722, 327 0.12183122, 0.58905521, 0.54562298, 0.12114807, 0.59273889, 0.54464114, 328 0.12056501, 0.59642187, 0.54361058, 0.12009154, 0.60010387, 0.54253043, 329 0.11973756, 0.60378459, 0.54139999, 0.11951163, 0.60746388, 0.54021751, 330 0.11942341, 0.61114146, 0.53898192, 0.11948255, 0.61481702, 0.53769219, 331 0.11969858, 0.61849025, 0.53634733, 0.12008079, 0.62216081, 0.53494633, 332 0.12063824, 0.62582833, 0.53348834, 0.12137972, 0.62949242, 0.53197275, 333 0.12231244, 0.63315277, 0.53039808, 0.12344358, 0.63680899, 0.52876343, 334 0.12477953, 0.64046069, 0.52706792, 0.12632581, 0.64410744, 0.52531069, 335 0.12808703, 0.64774881, 0.52349092, 0.13006688, 0.65138436, 0.52160791, 336 0.13226797, 0.65501363, 0.51966086, 0.13469183, 0.65863619, 0.5176488, 337 0.13733921, 0.66225157, 0.51557101, 0.14020991, 0.66585927, 0.5134268, 338 0.14330291, 0.66945881, 0.51121549, 0.1466164, 0.67304968, 0.50893644, 339 0.15014782, 0.67663139, 0.5065889, 0.15389405, 0.68020343, 0.50417217, 340 0.15785146, 0.68376525, 0.50168574, 0.16201598, 0.68731632, 0.49912906, 341 0.1663832, 0.69085611, 0.49650163, 0.1709484, 0.69438405, 0.49380294, 342 0.17570671, 0.6978996, 0.49103252, 0.18065314, 0.70140222, 0.48818938, 343 0.18578266, 0.70489133, 0.48527326, 0.19109018, 0.70836635, 0.48228395, 344 0.19657063, 0.71182668, 0.47922108, 0.20221902, 0.71527175, 0.47608431, 345 0.20803045, 0.71870095, 0.4728733, 0.21400015, 0.72211371, 0.46958774, 346 0.22012381, 0.72550945, 0.46622638, 0.2263969, 0.72888753, 0.46278934, 347 0.23281498, 0.73224735, 0.45927675, 0.2393739, 0.73558828, 0.45568838, 348 0.24606968, 0.73890972, 0.45202405, 0.25289851, 0.74221104, 0.44828355, 349 0.25985676, 0.74549162, 0.44446673, 0.26694127, 0.74875084, 0.44057284, 350 0.27414922, 0.75198807, 0.4366009, 0.28147681, 0.75520266, 0.43255207, 351 0.28892102, 0.75839399, 0.42842626, 0.29647899, 0.76156142, 0.42422341, 352 0.30414796, 0.76470433, 0.41994346, 0.31192534, 0.76782207, 0.41558638, 353 0.3198086, 0.77091403, 0.41115215, 0.3277958, 0.77397953, 0.40664011, 354 0.33588539, 0.7770179, 0.40204917, 0.34407411, 0.78002855, 0.39738103, 355 0.35235985, 0.78301086, 0.39263579, 0.36074053, 0.78596419, 0.38781353, 356 0.3692142, 0.78888793, 0.38291438, 0.37777892, 0.79178146, 0.3779385, 357 0.38643282, 0.79464415, 0.37288606, 0.39517408, 0.79747541, 0.36775726, 358 0.40400101, 0.80027461, 0.36255223, 0.4129135, 0.80304099, 0.35726893, 359 0.42190813, 0.80577412, 0.35191009, 0.43098317, 0.80847343, 0.34647607, 360 0.44013691, 0.81113836, 0.3409673, 0.44936763, 0.81376835, 0.33538426, 361 0.45867362, 0.81636288, 0.32972749, 0.46805314, 0.81892143, 0.32399761, 362 0.47750446, 0.82144351, 0.31819529, 0.4870258, 0.82392862, 0.31232133, 363 0.49661536, 0.82637633, 0.30637661, 0.5062713, 0.82878621, 0.30036211, 364 0.51599182, 0.83115784, 0.29427888, 0.52577622, 0.83349064, 0.2881265, 365 0.5356211, 0.83578452, 0.28190832, 0.5455244, 0.83803918, 0.27562602, 366 0.55548397, 0.84025437, 0.26928147, 0.5654976, 0.8424299, 0.26287683, 367 0.57556297, 0.84456561, 0.25641457, 0.58567772, 0.84666139, 0.24989748, 368 0.59583934, 0.84871722, 0.24332878, 0.60604528, 0.8507331, 0.23671214, 369 0.61629283, 0.85270912, 0.23005179, 0.62657923, 0.85464543, 0.22335258, 370 0.63690157, 0.85654226, 0.21662012, 0.64725685, 0.85839991, 0.20986086, 371 0.65764197, 0.86021878, 0.20308229, 0.66805369, 0.86199932, 0.19629307, 372 0.67848868, 0.86374211, 0.18950326, 0.68894351, 0.86544779, 0.18272455, 373 0.69941463, 0.86711711, 0.17597055, 0.70989842, 0.86875092, 0.16925712, 374 0.72039115, 0.87035015, 0.16260273, 0.73088902, 0.87191584, 0.15602894, 375 0.74138803, 0.87344918, 0.14956101, 0.75188414, 0.87495143, 0.14322828, 376 0.76237342, 0.87642392, 0.13706449, 0.77285183, 0.87786808, 0.13110864, 377 0.78331535, 0.87928545, 0.12540538, 0.79375994, 0.88067763, 0.12000532, 378 0.80418159, 0.88204632, 0.11496505, 0.81457634, 0.88339329, 0.11034678, 379 0.82494028, 0.88472036, 0.10621724, 0.83526959, 0.88602943, 0.1026459, 380 0.84556056, 0.88732243, 0.09970219, 0.8558096, 0.88860134, 0.09745186, 381 0.86601325, 0.88986815, 0.09595277, 0.87616824, 0.89112487, 0.09525046, 382 0.88627146, 0.89237353, 0.09537439, 0.89632002, 0.89361614, 0.09633538, 383 0.90631121, 0.89485467, 0.09812496, 0.91624212, 0.89609127, 0.1007168, 384 0.92610579, 0.89732977, 0.10407067, 0.93590444, 0.8985704, 0.10813094, 385 0.94563626, 0.899815, 0.11283773, 0.95529972, 0.90106534, 0.11812832, 386 0.96489353, 0.90232311, 0.12394051, 0.97441665, 0.90358991, 0.13021494, 387 0.98386829, 0.90486726, 0.13689671, 0.99324789, 0.90615657, 0.1439362, 388 ] 389 390 # I'm using data straight from the original implementation of Parula 391 # by Nathaniel Smith & Stefan van der Walt: 392 # https://github.com/BIDS/colormap/blob/master/parula.py 393 parula = [ 394 0.2081, 0.1663, 0.5292, 0.2116238095, 0.1897809524, 0.5776761905, 395 0.212252381, 0.2137714286, 0.6269714286, 0.2081, 0.2386, 0.6770857143, 396 0.1959047619, 0.2644571429, 0.7279, 0.1707285714, 0.2919380952, 397 0.779247619, 0.1252714286, 0.3242428571, 0.8302714286, 398 0.0591333333, 0.3598333333, 0.8683333333, 0.0116952381, 0.3875095238, 399 0.8819571429, 0.0059571429, 0.4086142857, 0.8828428571, 400 0.0165142857, 0.4266, 0.8786333333, 0.032852381, 0.4430428571, 401 0.8719571429, 0.0498142857, 0.4585714286, 0.8640571429, 402 0.0629333333, 0.4736904762, 0.8554380952, 0.0722666667, 0.4886666667, 403 0.8467, 0.0779428571, 0.5039857143, 0.8383714286, 404 0.079347619, 0.5200238095, 0.8311809524, 0.0749428571, 0.5375428571, 405 0.8262714286, 0.0640571429, 0.5569857143, 0.8239571429, 406 0.0487714286, 0.5772238095, 0.8228285714, 0.0343428571, 0.5965809524, 407 0.819852381, 0.0265, 0.6137, 0.8135, 0.0238904762, 0.6286619048, 408 0.8037619048, 0.0230904762, 0.6417857143, 0.7912666667, 409 0.0227714286, 0.6534857143, 0.7767571429, 0.0266619048, 0.6641952381, 410 0.7607190476, 0.0383714286, 0.6742714286, 0.743552381, 411 0.0589714286, 0.6837571429, 0.7253857143, 412 0.0843, 0.6928333333, 0.7061666667, 0.1132952381, 0.7015, 0.6858571429, 413 0.1452714286, 0.7097571429, 0.6646285714, 0.1801333333, 0.7176571429, 414 0.6424333333, 0.2178285714, 0.7250428571, 0.6192619048, 415 0.2586428571, 0.7317142857, 0.5954285714, 0.3021714286, 0.7376047619, 416 0.5711857143, 0.3481666667, 0.7424333333, 0.5472666667, 417 0.3952571429, 0.7459, 0.5244428571, 0.4420095238, 0.7480809524, 418 0.5033142857, 0.4871238095, 0.7490619048, 0.4839761905, 419 0.5300285714, 0.7491142857, 0.4661142857, 0.5708571429, 0.7485190476, 420 0.4493904762, 0.609852381, 0.7473142857, 0.4336857143, 421 0.6473, 0.7456, 0.4188, 0.6834190476, 0.7434761905, 0.4044333333, 422 0.7184095238, 0.7411333333, 0.3904761905, 423 0.7524857143, 0.7384, 0.3768142857, 0.7858428571, 0.7355666667, 424 0.3632714286, 0.8185047619, 0.7327333333, 0.3497904762, 425 0.8506571429, 0.7299, 0.3360285714, 0.8824333333, 0.7274333333, 0.3217, 426 0.9139333333, 0.7257857143, 0.3062761905, 0.9449571429, 0.7261142857, 427 0.2886428571, 0.9738952381, 0.7313952381, 0.266647619, 428 0.9937714286, 0.7454571429, 0.240347619, 0.9990428571, 0.7653142857, 429 0.2164142857, 0.9955333333, 0.7860571429, 0.196652381, 430 0.988, 0.8066, 0.1793666667, 0.9788571429, 0.8271428571, 0.1633142857, 431 0.9697, 0.8481380952, 0.147452381, 0.9625857143, 0.8705142857, 0.1309, 432 0.9588714286, 0.8949, 0.1132428571, 0.9598238095, 0.9218333333, 433 0.0948380952, 0.9661, 0.9514428571, 0.0755333333, 434 0.9763, 0.9831, 0.0538 435 ] 436 437 438 def beta(x, y): 439 return gamma(x) * gamma(y) / gamma(x + y) 440 441 def clamp(x, lowest, highest): 442 ''' 443 Keep the first number given between the other two. The second number 444 is supposed to be less than or equal to the third number given. 445 ''' 446 return max(lowest, min(x, highest)) 447 448 def deinf(x, default): 449 '''Replace either kind of floating-point infinity with another value.''' 450 return x if not isinf(x) else default 451 452 def denan(x, default): 453 '''Replace a floating-point NaN with an alternative value.''' 454 return x if not isinf(x) else default 455 456 def lbeta(x, y): 457 return lgamma(x) + lgamma(y) - lgamma(x + y) 458 459 def log(x, base = e): 460 try: 461 return math_log(x, base) 462 except Exception as _: 463 return nan 464 465 def osc(x): 466 ''' 467 This `oscillator` func is just a handy shortcut to calculate the sine, 468 after multiplying the argument by 2 * pi. 469 ''' 470 return sin(tau * x) 471 472 def revalue(x, default): 473 '''Replace floating-point NaNs and infinities with another value.''' 474 return x if not (isinf(x) or isnan(x)) else default 475 476 def scale(x, x0, x1, y0, y1): 477 return ((x - x0) / (x1 - x0)) * (y1 - y0) + y0 478 479 def sign(x): 480 if isnan(x): 481 return nan 482 return +1.0 if x > 0.0 else (-1.0 if x < 0.0 else 0.0) 483 484 sgn = sign 485 486 def sinc(x): 487 return sin(x) / x if x != 0.0 else 1.0 488 489 def sqrt(x): 490 try: 491 return math_sqrt(x) 492 except Exception as _: 493 return nan 494 495 def wrap(x, y0, y1): 496 return x * (y1 - y0) + y0 497 498 499 def write_bmp(w, grid, low, high, width, height): 500 dib_size = 40 501 hdr_size = 14 + dib_size 502 img_size = 3 * width * height 503 w = w.write 504 505 w(b'BM') 506 w(pack('<I', hdr_size + img_size)) 507 w(pack('<H', 0)) 508 w(pack('<H', 0)) 509 w(pack('<I', hdr_size)) 510 w(pack('<I', dib_size)) 511 w(pack('<i', width)) 512 w(pack('<i', height)) 513 514 w(pack('<H', 1)) # 1 color plane 515 w(pack('<H', 24)) # 24 bits per pixel 516 w(pack('<I', 0)) # no compression 517 w(pack('<I', img_size)) # number of bytes for the pixels 518 w(pack('<i', 0)) # horizonal and vertical pixels/m 519 w(pack('<i', 0)) # 2**n palette colors 520 w(pack('<I', 0)) # all colors are important 521 w(pack('<I', 0)) 522 523 span = high - low 524 bad = b'\x00\x00\xff' # color-channel order seems to be BGR, not RGB 525 bgr = bytearray(b'\x00\x00\x00') 526 527 palette = viridis 528 # palette = parula 529 k = float(len(palette) / 3 - 1) 530 531 for line in grid: 532 for z in line: 533 if isnan(z) or isinf(z): 534 w(bad) 535 continue 536 v = int(round(k * (z - low) / span)) 537 i = 3 * v 538 bgr[2] = int(255.0 * palette[i + 0]) 539 bgr[1] = int(255.0 * palette[i + 1]) 540 bgr[0] = int(255.0 * palette[i + 2]) 541 w(bgr) 542 543 544 def parse_range(s): 545 pieces = s.split('..') 546 if len(pieces) == 2: 547 try: 548 return float(pieces[0]), float(pieces[1]) 549 except Exception as _: 550 pass 551 return nan, nan 552 553 554 square_output = False 555 hd_opts = ('-hd', '--hd', '-1080p', '--1080p') 556 square_opts = ('-s', '--s', '-square', '--square', '-squared', '--squared') 557 558 src = '' 559 width = 1920 * 2 560 height = 1080 * 2 561 xmin = 0.0 562 xmax = 1.0 563 ymin = 0.0 564 ymax = 1.0 565 566 args = argv[1:] 567 568 while len(args) > 0: 569 if args[0] == '-': 570 break 571 572 if args[0] == '--': 573 args = args[1:] 574 break 575 576 if args[0] in hd_opts: 577 width = 1920 578 height = 1080 579 args = args[1:] 580 continue 581 582 if args[0] in square_opts: 583 square_output = True 584 args = args[1:] 585 continue 586 587 if args[0].startswith('-xy=') or args[0].startswith('--xy='): 588 x, y = parse_range(args[0][args[0].index('=') + 1:]) 589 if isnan(x): 590 break 591 args = args[1:] 592 xmin = ymin = x 593 xmax = ymax = y 594 continue 595 596 if args[0].startswith('-x=') or args[0].startswith('--x='): 597 x, y = parse_range(args[0][args[0].index('=') + 1:]) 598 if isnan(x): 599 break 600 args = args[1:] 601 xmin = x 602 xmax = y 603 continue 604 605 if args[0].startswith('-y=') or args[0].startswith('--y='): 606 x, y = parse_range(args[0][args[0].index('=') + 1:]) 607 if isnan(x): 608 break 609 args = args[1:] 610 ymin = x 611 ymax = y 612 continue 613 614 if args[0].startswith('-'): 615 from sys import stderr 616 print(f'fh: unsupported option {args[0]}', file=stderr) 617 exit(1) 618 619 break 620 621 if square_output: 622 width = height 623 624 if len(args) != 1: 625 from sys import exit, stderr 626 print(info.strip(), file=stderr) 627 exit(1) 628 629 630 x = 0.0 631 y = 0.0 632 prev = 0.0 633 if xmax < xmin: 634 xmin, xmax = xmax, xmin 635 if ymax < ymin: 636 ymin, ymax = ymax, ymin 637 dx = (xmax - xmin) / (width - 1.0) 638 dy = (ymax - ymin) / (height - 1.0) 639 w = width 640 h = height 641 low = +inf 642 high = -inf 643 644 src = args[0] 645 grid = [[0.0] * width for _ in range(height)] 646 dest = stdout.buffer 647 648 try: 649 # giving func eval a compiled string considerably speeds things up 650 expr = compile(src, '<string>', 'eval') 651 # prevent expression from using some global values 652 compile = exec = exit = open = stdout = None 653 654 for j in range(height): 655 row = grid[j] 656 y = float(height - 1 - j) * dy + ymin 657 658 for i in range(width): 659 x = float(i) * dx + xmin 660 prev = eval(expr) 661 row[i] = prev 662 663 if isinf(prev) or isnan(prev): 664 continue 665 666 low = min(low, prev) 667 high = max(high, prev) 668 669 if isnan(low) or isnan(high): 670 msg = 'function is completely undefined in the sampled (x, y) region' 671 raise ValueError(msg) 672 if isinf(low) or isinf(high): 673 msg = 'function spans an infinite range in the sampled (x, y) region' 674 raise ValueError(msg) 675 write_bmp(dest, grid, low, high, width, height) 676 except KeyboardInterrupt: 677 from sys import exit 678 exit(2) 679 except OSError as _: 680 # quit quietly, instead of showing a confusing error message 681 from sys import exit, stderr 682 stderr.close() 683 exit(0) 684 except Exception as e: 685 # raise e 686 from sys import exit, stderr 687 print(str(e), file=stderr) 688 exit(1)