Repository: vispy/vispy

Issue:
Minor inconsistency in the number of arguments between gloo.set_clear_color and gloo.set_viewport
`gloo.set_clear_color` accepts 1 tuple of 4 floats, but `gloo.set_viewport` accepts 4 floats. Might be slightly confusing?



Discussion:
I'm fine with it either way, PR welcome if others agree :)

I think I prefer separate arguments, because `set_viewport(*args)` looks nicer than `set_clear_color((r, g, b, a))`.

I forgot part of the motivation for `set_clear_color` taking a tuple is that it allows one to do `set_state(clear_color=(0, 0, 0, 0))` and the wrapping is very simple. Basically anything that works with `set_state` needs to take a single argument. Otherwise a special catch will need to go in the wrapper to check and distribute arguments. We can probably work out a clean solution under the hood if separate arguments is more intuitive for people.

I actually just remembered a different (and probably better) motivation for `set_clear_color(color)` instead of `set_clear_color(r, g, b, a)`. The first will eventually be more pythonic / user-friendly soon, because one will be able to just do `set_clear_color('white')` once `vispy.colors` exists. I'm not sure if there is any analogous functionality planned for `set_viewport` (I doubt it), but that's an argument for changing `set_viewport` to be `set_viewport(view)` instead of `set_viewport(x, y, w, h)` for consistency.

I have a slight preference for a single argument instead of 4. The point is
essentially to have consistency between the methods. `set_viewport((x, y,
w, h))` would be fine by me.

+1. Are you volunteering to make a PR? :)

Sure, @almarklein @rougier are you ok with this decision?

Fine by me.

Why not support both methods?

```
def foo(*x):
    if len(x)==1 and isinstance(x, tuple):
        x = x[0]
```

That's not the most elegant thing in the world (there should be only 1 way
to do something), but why not. Maybe it could be implemented in a decorator?

> That's not the most elegant thing in the world

But `set_clear_color((r, g, b, a))` also looks a bit clumsy :)  If I had to chose, I think I would prefer having four arguments, because if one has four values, it would look right, and if one had a tuple, one can use `set_clear_color(*clr)`.

@almarklein but in the hopefully near future, one would just be able to say `set_clear_color('white')` once `vispy.colors` works. This is an argument for having a single input, which can be a string, vispy `Color`, tuple of floats, or whatever. Having separate arguments for each component is not very future-compatible.

> Having separate arguments for each component is not very future-compatible.

Ah, doh. You are right. Ok, then my preference is for allowing both, but I am ok if it is decided to support only single-arguments.

It does look clumsy to have those double parentheses, but that's already
the case in most NumPy functions, so people should be used to it already. ;)

On Sat, May 10, 2014 at 4:14 PM, Eric89GXL notifications@github.com wrote:

> @almarklein https://github.com/almarklein but in the hopefully near
> future, one would just be able to say set_clear_color('white') once
> vispy.colors works. This is an argument for having a single input, which
> can be a string, vispy Color, tuple of floats, or whatever. Having
> separate arguments for each component is not very future-compatible.
> 
> Disagree--it is not at all difficult to ducktype between a string, a tuple,
> and 3 or 4 integers.

In pyqtgraph, all setColor methods have the same signature, which includes
nearly every possible format you might conceive:

```
setColor(r, g, b)
setColor(r, g, b, a)
setColor((r, g, b))
setColor(ndarray)
setColor(l)  # luminance
setColor('w')
. . .
```

I am usually opposed to excessive ducktyping, but if the logical input to
the function is _only_ a color, then it seems natural to allow that color
to be specified in a variety of ways.

On the other hand, there will be many other places in the package (probably most places?) where `color` is just one of multiple arguments, so in those places the colors will need to be passed as tuples if one uses `(r, g, b, a)` format. This would make our handling of colors inconsistent to allow `func(r, g, b, a)` for some functions (this one, maybe a few others), and have do something else (single input) everywhere else. So I'm still -1 on the multi-arguments.

It would be good to get this fixed before 0.3 so that we don't need to make an API change. I can take care of it. It sounds like we have settled on one argument for `set_viewport` for consistency with other `gloo` functions like `set_clear_color`, is that still okay with people? cc @rossant @almarklein @lcampagn 

> we have settled on one argument for set_viewport for consistency with other gloo functions like set_clear_color, is that still okay with people?

+1

I'm leaning towards allowing both syntaxes. Mainly because of the awkwardness of `set_viewport((x, y, w, h))`.

It should be easy to implement both, I'll do that.


Resolving patch:
diff --git a/vispy/gloo/wrappers.py b/vispy/gloo/wrappers.py
--- a/vispy/gloo/wrappers.py
+++ b/vispy/gloo/wrappers.py
@@ -73,22 +73,18 @@ def _check_conversion(key, valid_dict):
 # Viewport, DepthRangef, CullFace, FrontFace, LineWidth, PolygonOffset
 #
 
-def set_viewport(x, y, w, h):
+def set_viewport(*args):
     """Set the OpenGL viewport
 
     This is a wrapper for gl.glViewport.
 
     Parameters
     ----------
-    x : int
-        X coordinate.
-    y : int
-        Y coordinate.
-    w : int
-        Viewport width.
-    h : int
-        Viewport height.
+    x, y, w, h : int | tuple
+        X and Y coordinates, plus width and height. Can be passed in as
+        individual components, or as a single tuple with four values.
     """
+    x, y, w, h = args[0] if len(args) == 1 else args
     gl.glViewport(int(x), int(y), int(w), int(h))
 
 
diff --git a/vispy/scene/visuals/text/_sdf.py b/vispy/scene/visuals/text/_sdf.py
--- a/vispy/scene/visuals/text/_sdf.py
+++ b/vispy/scene/visuals/text/_sdf.py
@@ -251,12 +251,6 @@ def render_to_texture(self, data, texture, offset, size):
         size : tuple of int
             Size (w, h) to render inside the texture.
         """
-        offset = np.array(offset)
-        size = np.array(size)
-        for x in (offset, size):
-            assert x.size == 2 and x.ndim == 1
-            assert x.dtype.kind == 'i'
-        assert data.ndim == 2 and data.dtype == np.uint8
         assert isinstance(texture, Texture2D)
         set_state(blend=False, depth_test=False)
 
@@ -276,7 +270,7 @@ def render_to_texture(self, data, texture, offset, size):
         self.program_insert['u_neg_texture'] = edf_neg_tex
         self.fbo_to[-1].color_buffer = texture
         with self.fbo_to[-1]:
-            set_viewport(offset[0], offset[1], size[0], size[1])
+            set_viewport(tuple(offset) + tuple(size))
             self.program_insert.draw('triangle_strip')
 
     def _render_edf(self, orig_tex):


Test patch:
diff --git a/vispy/gloo/tests/test_use_gloo.py b/vispy/gloo/tests/test_use_gloo.py
--- a/vispy/gloo/tests/test_use_gloo.py
+++ b/vispy/gloo/tests/test_use_gloo.py
@@ -32,7 +32,7 @@ def test_use_framebuffer():
     rbo = ColorBuffer(shape=shape)
     fbo = FrameBuffer(color=fbo_tex)
     with Canvas(size=(100, 100)) as c:
-        set_viewport(0, 0, *c.size)
+        set_viewport((0, 0) + c.size)
         with fbo:
             draw_texture(orig_tex)
         draw_texture(fbo_tex)