Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Graphics overhault #1132

Closed
wants to merge 38 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
90aac8c
basic LayoutEngine working
poke1024 Aug 30, 2016
a253dd7
adds FontSize
poke1024 Aug 30, 2016
a156481
added --svg-math parameter to enable layout engine; not used by default
poke1024 Aug 31, 2016
50dbf64
LayoutEngine constructor now raises Exceptions
poke1024 Aug 31, 2016
bd08c19
fixes error reporting
poke1024 Aug 31, 2016
aaf113f
error handling cosmetics
poke1024 Aug 31, 2016
6a3ec6b
got rid of zerorpc since it is such a dependency hell
poke1024 Sep 1, 2016
f95027f
allow environment variables in node paths
poke1024 Sep 1, 2016
f0900ee
fixes NODE_MODULES
poke1024 Sep 1, 2016
24edc95
work in progress: mix mglyph and svg-only approach
poke1024 Sep 3, 2016
c30595d
several small improvement for more stability
poke1024 Sep 4, 2016
be7106e
major cleanup
poke1024 Sep 4, 2016
f5c5e6d
fixes node.js output; better warnings
poke1024 Sep 4, 2016
ef52066
basic version of Rasterize[], fixes for the classic Mathics frontend
poke1024 Sep 5, 2016
dcda65a
better error handling
poke1024 Sep 5, 2016
4d725a3
fixes Python 2 syntax error
poke1024 Sep 10, 2016
0116477
added nowebeng message
poke1024 Sep 14, 2016
c7c54c7
got rid of NODE and NODE_MODULES in settings.py; the cleanest way to …
poke1024 Sep 14, 2016
f9144b2
Rasterize[] changed to use PIL
poke1024 Sep 15, 2016
ea0b17a
fixes Rasterize[]: avoid embedding svgs in svgs
poke1024 Sep 15, 2016
6e694d8
fixes a bug in TerminalOutput
poke1024 Sep 15, 2016
e6bbb13
better node config/startup
poke1024 Oct 18, 2016
e51b5a6
merge with transforms
poke1024 Oct 21, 2016
44adcfd
merge transforms
poke1024 Oct 21, 2016
d9407ed
merge transforms
poke1024 Oct 21, 2016
72abc7a
merge transforms
poke1024 Oct 21, 2016
cb915dc
merge transforms
poke1024 Oct 21, 2016
6729d0c
merge transforms
poke1024 Oct 21, 2016
941a839
various fixes regarding text and points
poke1024 Oct 21, 2016
6c70521
fixes test case
poke1024 Oct 21, 2016
d7aeb05
preparations for Mathics computed transforms
poke1024 Nov 5, 2016
7bff167
fixes Plot[1+x*0.000001, {x, 0, 1}]
poke1024 Nov 5, 2016
86d4f1c
various cleanups and fixes
poke1024 Nov 5, 2016
fb2d1db
fixes test case
poke1024 Nov 5, 2016
e85bef5
fix node.js server startup
poke1024 Aug 30, 2020
8dd037a
partial merge to master
mmatera Jan 26, 2021
18b5e54
finish merging
mmatera Jan 26, 2021
5b1cae9
Merge remote-tracking branch 'upstream/master' into graphicsoverhault
mmatera Feb 14, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,058 changes: 857 additions & 201 deletions mathics/builtin/graphics.py

Large diffs are not rendered by default.

35 changes: 19 additions & 16 deletions mathics/builtin/graphics3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,21 +114,20 @@ class Graphics3D(Graphics):
. import three;
. import solids;
. size(6.6667cm, 6.6667cm);
. currentprojection=perspective(2.6,-4.8,4.0);
. currentprojection=perspective(1.3,-2.4,2.0);
. currentlight=light(rgb(0.5,0.5,1), specular=red, (2,0,2), (2,2,2), (0,2,2));
. path3 g=(0,1,0)--(0.20791,0.97815,0)--(0.40674,0.91355,0)--(0.58779,0.80902,0)--(0.74314,0.66913,0)--(0.86603,0.5,0)--(0.95106,0.30902,0)--(0.99452,0.10453,0)--(0.99452,-0.10453,0)--(0.95106,-0.30902,0)--(0.86603,-0.5,0)--(0.74314,-0.66913,0)--(0.58779,-0.80902,0)--(0.40674,-0.91355,0)--(0.20791,-0.97815,0)--(5.6655e-16,-1,0)--(-0.20791,-0.97815,0)--(-0.40674,-0.91355,0)--(-0.58779,-0.80902,0)--(-0.74314,-0.66913,0)--(-0.86603,-0.5,0)--(-0.95106,-0.30902,0)--(-0.99452,-0.10453,0)--(-0.99452,0.10453,0)--(-0.95106,0.30902,0)--(-0.86603,0.5,0)--(-0.74314,0.66913,0)--(-0.58779,0.80902,0)--(-0.40674,0.91355,0)--(-0.20791,0.97815,0)--(1.5314e-15,1,0)--cycle;dot(g, rgb(0, 0, 0));
. draw(((-0.99452,-1,-1)--(0.99452,-1,-1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
. draw(((-0.99452,1,-1)--(0.99452,1,-1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
. draw(((-0.99452,-1,1)--(0.99452,-1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
. draw(((-0.99452,1,1)--(0.99452,1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
. draw(((-0.99452,-1,-1)--(-0.99452,1,-1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
. draw(((0.99452,-1,-1)--(0.99452,1,-1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
. draw(((-0.99452,-1,1)--(-0.99452,1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
. draw(((0.99452,-1,1)--(0.99452,1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
. draw(((-0.99452,-1,-1)--(-0.99452,-1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
. draw(((0.99452,-1,-1)--(0.99452,-1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
. draw(((-0.99452,1,-1)--(-0.99452,1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
. draw(((0.99452,1,-1)--(0.99452,1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
. draw(((0,0,0)--(1,0,0)), rgb(0.4, 0.4, 0.4)+linewidth(1));
. draw(((0,1,0)--(1,1,0)), rgb(0.4, 0.4, 0.4)+linewidth(1));
. draw(((0,0,1)--(1,0,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
. draw(((0,1,1)--(1,1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
. draw(((0,0,0)--(0,1,0)), rgb(0.4, 0.4, 0.4)+linewidth(1));
. draw(((1,0,0)--(1,1,0)), rgb(0.4, 0.4, 0.4)+linewidth(1));
. draw(((0,0,1)--(0,1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
. draw(((1,0,1)--(1,1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
. draw(((0,0,0)--(0,0,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
. draw(((1,0,0)--(1,0,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
. draw(((0,1,0)--(0,1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
. draw(((1,1,0)--(1,1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
. \end{asy}
"""

Expand Down Expand Up @@ -763,8 +762,6 @@ def total_extent_3d(extents):


class Graphics3DElements(_GraphicsElements):
coords = Coords3D

def __init__(self, content, evaluation, neg_y=False):
super(Graphics3DElements, self).__init__(content, evaluation)
self.neg_y = neg_y
Expand All @@ -774,6 +771,10 @@ def __init__(self, content, evaluation, neg_y=False):
self.pixel_width
) = self.pixel_height = self.extent_width = self.extent_height = None

def make_coords(self, points):
print("make_coords3d",points)
return [Coords3D(self, p) for p in points]

def extent(self, completely_visible_only=False):
return total_extent_3d([element.extent() for element in self.elements])

Expand All @@ -799,6 +800,7 @@ def get_style_class(self):

class Point3DBox(PointBox):
def init(self, *args, **kwargs):
print("Point3DBox.init")
super(Point3DBox, self).init(*args, **kwargs)

def process_option(self, name, value):
Expand Down Expand Up @@ -902,6 +904,7 @@ def _apply_boxscaling(self, boxscale):
class Polygon3DBox(PolygonBox):
def init(self, *args, **kwargs):
self.vertex_normals = None
self.vertex_colors = None
super(Polygon3DBox, self).init(*args, **kwargs)

def process_option(self, name, value):
Expand Down
42 changes: 41 additions & 1 deletion mathics/builtin/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
convert as convert_color,
colorspaces as known_colorspaces,
)
#from mathics.layout.client import WebEngineError

import base64
import functools
Expand Down Expand Up @@ -2569,4 +2570,43 @@ def color_func(
wc.generate_from_frequencies(freq)

image = wc.to_image()
return Image(numpy.array(image), "RGB")
return Image(numpy.array(image), 'RGB')


class Rasterize(Builtin):
requires = _image_requires

options = {
'RasterSize': '300',
}

def apply(self, expr, evaluation, options):
'Rasterize[expr_, OptionsPattern[%(name)s]]'

raster_size = self.get_option(options, 'RasterSize', evaluation)
if isinstance(raster_size, Integer):
s = raster_size.get_int_value()
py_raster_size = (s, s)
elif raster_size.has_form('List', 2) and all(isinstance(s, Integer) for s in raster_size.leaves):
py_raster_size = tuple(s.get_int_value for s in raster_size.leaves)
else:
return

mathml = evaluation.format_output(expr, 'xml')
try:
svg = evaluation.output.mathml_to_svg(mathml)
png = evaluation.output.rasterize(svg, py_raster_size)

stream = BytesIO()
stream.write(png)
stream.seek(0)
im = PIL.Image.open(stream)
# note that we need to get these pixels as long as stream is still open,
# otherwise PIL will generate an IO error.
pixels = numpy.array(im)
stream.close()

return Image(pixels, 'RGB')
except WebEngineError as e:
evaluation.message(
'General', 'nowebeng', 'Rasterize[] did not succeed: ' + str(e), once=True)
1 change: 1 addition & 0 deletions mathics/builtin/inout.py
Original file line number Diff line number Diff line change
Expand Up @@ -1878,6 +1878,7 @@ class General(Builtin):
'notboxes': "`1` is not a valid box structure.",

'pyimport': "`1`[] is not available. Your Python installation misses the \"`2`\" module.",
'nowebeng': "Web Engine is not available: `1`",
}


Expand Down
13 changes: 13 additions & 0 deletions mathics/builtin/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,14 @@ def find_excl(excl):
meshpoints = [Expression("List", xx, yy) for xx, yy in points]
graphics.append(Expression("Point", Expression("List", *meshpoints)))

# we need the PrecomputeTransformations option here. to understand why, try Plot[1+x*0.000001, {x, 0, 1}]
# without it. in Graphics[], we set up a transformation that scales a very tiny area to a very large area.
# unfortunately, most browsers seem to have problems with scaling stroke width properly. since we scale a
# very tiny area, we specify a very small stroke width (e.g. 1e-6) which is then scaled. but most browsers
# simply round this stroke width to 0 before scaling, so we end up with an empty plot. in order to fix this,
# Transformation -> Precomputed simply gets rid of the SVG transformations and passes the scaled coordinates
# into the SVG. this also has the advantage that we can precompute with arbitrary precision using mpmath.
options['System`Transformation'] = String('Precomputed')
return Expression(
"Graphics", Expression("List", *graphics), *options_to_rules(options)
)
Expand Down Expand Up @@ -1243,6 +1251,11 @@ def labels(names):
)







class Histogram(Builtin):
"""
<dl>
Expand Down
97 changes: 97 additions & 0 deletions mathics/builtin/tensors.py
Original file line number Diff line number Diff line change
Expand Up @@ -482,3 +482,100 @@ def is_boolean(x):
return 'ColorDistance'

return None



class TransformationFunction(Builtin):
"""
<dl>
<dt>'TransformationFunction[$m$]'
<dd>represents a transformation.
</dl>

>> RotationTransform[Pi].TranslationTransform[{1, -1}]
= TransformationFunction[{{-1, 0, -1}, {0, -1, 1}, {0, 0, 1}}]

>> TranslationTransform[{1, -1}].RotationTransform[Pi]
= TransformationFunction[{{-1, 0, 1}, {0, -1, -1}, {0, 0, 1}}]
"""

rules = {
'Dot[TransformationFunction[a_], TransformationFunction[b_]]': 'TransformationFunction[a . b]',
'TransformationFunction[m_][v_]': 'Take[m . Join[v, {1}], Length[v]]',
}


class TranslationTransform(Builtin):
"""
<dl>
<dt>'TranslationTransform[$v$]'
<dd>gives the translation by the vector $v$.
</dl>

>> TranslationTransform[{1, 2}]
= TransformationFunction[{{1, 0, 1}, {0, 1, 2}, {0, 0, 1}}]
"""

rules = {
'TranslationTransform[v_]':
'TransformationFunction[IdentityMatrix[Length[v] + 1] + '
'(Join[ConstantArray[0, Length[v]], {#}]& /@ Join[v, {0}])]',
}


class RotationTransform(Builtin):
"""
<dl>
<dt>'RotationTransform[$phi$]'
<dd>gives a rotation by $phi$.
<dt>'RotationTransform[$phi$, $p$]'
<dd>gives a rotation by $phi$ around the point $p$.
</dl>
"""

rules = {
'RotationTransform[phi_]':
'TransformationFunction[{{Cos[phi], -Sin[phi], 0}, {Sin[phi], Cos[phi], 0}, {0, 0, 1}}]',
'RotationTransform[phi_, p_]':
'TranslationTransform[p] . RotationTransform[phi] . TranslationTransform[-p]',
}


class ScalingTransform(Builtin):
"""
<dl>
<dt>'ScalingTransform[$v$]'
<dd>gives a scaling transform of $v$. $v$ may be a scalar or a vector.
<dt>'ScalingTransform[$phi$, $p$]'
<dd>gives a scaling transform of $v$ that is centered at the point $p$.
</dl>
"""

rules = {
'ScalingTransform[v_]':
'TransformationFunction[DiagonalMatrix[Join[v, {1}]]]',
'ScalingTransform[v_, p_]':
'TranslationTransform[p] . ScalingTransform[v] . TranslationTransform[-p]',
}


class ShearingTransform(Builtin):
"""
<dl>
<dt>'ShearingTransform[$phi$, {1, 0}, {0, 1}]'
<dd>gives a horizontal shear by the angle $phi$.
<dt>'ShearingTransform[$phi$, {0, 1}, {1, 0}]'
<dd>gives a vertical shear by the angle $phi$.
<dt>'ShearingTransform[$phi$, $u$, $u$, $p$]'
<dd>gives a shear centered at the point $p$.
</dl>
"""

rules = {
'ShearingTransform[phi_, {1, 0}, {0, 1}]':
'TransformationFunction[{{1, Tan[phi], 0}, {0, 1, 0}, {0, 0, 1}}]',
'ShearingTransform[phi_, {0, 1}, {1, 0}]':
'TransformationFunction[{{1, 0, 0}, {Tan[phi], 1, 0}, {0, 0, 1}}]',
'ShearingTransform[phi_, u_, v_, p_]':
'TranslationTransform[p] . ShearingTransform[phi, u, v] . TranslationTransform[-p]',
}
26 changes: 25 additions & 1 deletion mathics/core/evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@
from mathics_scanner import TranslateError

from mathics import settings

from mathics.layout.client import NoWebEngine
from mathics.core.expression import ensure_context, KeyComparable, SymbolAborted


FORMATS = [
"StandardForm",
"FullForm",
Expand Down Expand Up @@ -213,6 +216,9 @@ def get_data(self):


class Output(object):
def __init__(self, web_engine=NoWebEngine()):
self.web_engine = web_engine

def max_stored_size(self, settings) -> int:
return settings.MAX_STORED_SIZE

Expand All @@ -225,6 +231,18 @@ def clear(self, wait):
def display(self, data, metadata):
raise NotImplementedError

def warn_about_web_engine(self):
return False

def assume_web_engine(self):
return self.web_engine.assume_is_available()

def mathml_to_svg(self, mathml):
return self.web_engine.mathml_to_svg(mathml)

def rasterize(self, svg, *args, **kwargs):
return self.web_engine.rasterize(svg, *args, **kwargs)


class Evaluation(object):
def __init__(
Expand All @@ -249,6 +267,7 @@ def __init__(
self.quiet_all = False
self.format = format
self.catch_interrupt = catch_interrupt
self.once_messages = set()

self.SymbolNull = Symbol("Null")

Expand Down Expand Up @@ -474,7 +493,7 @@ def get_quiet_messages(self):
return []
return value.leaves

def message(self, symbol, tag, *args) -> None:
def message(self, symbol, tag, *args, **kwargs) -> None:
from mathics.core.expression import String, Symbol, Expression, from_python

# Allow evaluation.message('MyBuiltin', ...) (assume
Expand All @@ -484,6 +503,11 @@ def message(self, symbol, tag, *args) -> None:

pattern = Expression("MessageName", Symbol(symbol), String(tag))

if kwargs.get('once', False):
if pattern in self.once_messages:
return
self.once_messages.add(pattern)

if pattern in quiet_messages or self.quiet_all:
return

Expand Down
2 changes: 2 additions & 0 deletions mathics/layout/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
Loading