Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

I want to make some button shaped graphics that would essentially be a rectangular shape with curved edges. In the example below I have used Polygon rather than Rectangle so as to take advantage of VertexColors and have a gradient fill. The code below illustrates the sort of thing I want in so far as the Frame with RoundingRadius shows where I want the boundaries of the Graphic to be cut off (for example).

Framed[Graphics[{
   Polygon[{{0, 0}, {1, 0}, {1, 1}, {0, 1}}, 
    VertexColors -> {Red, Red, Blue, Blue}]
   },
  AspectRatio -> 0.2,
  ImagePadding -> 0,
  ImageMargins -> 0,
  ImageSize -> 200,
  PlotRangePadding -> 0],
 ContentPadding -> True,
 FrameMargins -> 0,
 ImageMargins -> 0,
 RoundingRadius -> 20]

I'm thinking there is probably a very straight forward way of accomplishing this. Is there some way to exclude parts of the Graphic that fall outside the Frame from displaying? Any alternative methods would be welcome.

Edit

I had been expecting that this was going to be possible with existing options rather than having to write functions. @Mr.Wizard provided a concise solution from existing built in functionality but I ultimately didn't want a raster solution. @Heike used RegionPlot like the others, but in a way in which the user, i.e. me, could implement it by simply changing a rounding radius parameter, so that makes it a more straight forward solution IMO.

share|improve this question
    
You are aware that Rectangle accepts the RoundingRadius option, right? Also, is rasterization acceptable? –  Mr.Wizard Jun 8 '12 at 7:37
    
@Mr.Wizard yes but in this instance I went with Polygon so as to make the gradient easy. If you can show me how to do the gradient fill in the rounded rectangle that will do fine. Would prefer no rasterization at this stage. –  Mike Honeychurch Jun 8 '12 at 7:44
    
Okay, I figured as much. No, I don't know how to get the fill and keep it vector. That +1 is mine. All of this would be easy if Mathematica could do proper vector graphics intersections. –  Mr.Wizard Jun 8 '12 at 7:47
    
@Mr.Wizard it looks like a non raster solution is either going to be very lengthy or very unsatisfactory. What was your rasterized solution? –  Mike Honeychurch Jun 8 '12 at 8:31
    
Related: mathematica.stackexchange.com/q/1882/121 –  Mr.Wizard Jun 10 '12 at 0:06
add comment

5 Answers

up vote 8 down vote accepted

This answer uses RegionPlot to plot the rounded rectangle. In roundedRect, {{xmin, xmax}, {ymin, ymax}} is the range of the rectangle and rad the rounding radius. roundedRect accepts any option of RegionPlot, in particular ColorFunction which you can use to shade the rectangle.

Options[roundedRect] = Options[RegionPlot];
SetOptions[roundedRect, {Frame -> False, Axes -> False, BoundaryStyle -> None}];

roundedRect[range : {{xmin_, xmax_}, {ymin_, ymax_}}, rad_, 
  opt : OptionsPattern[roundedRect]] := Module[{p, norm},
  p = 1/Log2[Sqrt[2] + 2];
  norm[pt_, pt0_] := Total[Abs[pt - pt0]^p]^(1/p) > rad;
  RegionPlot[And @@ (norm[{x, y}, #] & /@ Tuples[range]),
   {x, xmin, xmax}, {y, ymin, ymax}, opt,
   AspectRatio -> Abs[ymax - ymin]/Abs[xmax - xmin],
   Evaluate[Options[roundedRect]]]]

Example

roundedRect[{{0, 5}, {0, 1}}, .4, ColorFunction -> (Blend[{Red, Blue}, #2] &)]

Mathematica graphics

Edit

@Heike I hope you do not mind me making a change to your answer. I think this is more Mathematica like by having the rounding radius as an option.

ClearAll[roundedRect];

Options[roundedRect] = Flatten[{RoundingRadius -> 0.5, Options[RegionPlot]}];
SetOptions[roundedRect, {Frame -> False, Axes -> False, BoundaryStyle -> None}];

roundedRect[range : {{xmin_, xmax_}, {ymin_, ymax_}}, 
  opt : OptionsPattern[roundedRect]] := Module[{p, norm, opts, rad},

  rad = OptionValue[RoundingRadius];
  opts = FilterRules[{opt}, Options[RegionPlot]];

  p = 1/Log2[Sqrt[2] + 2];
  norm[pt_, pt0_] := Total[Abs[pt - pt0]^p]^(1/p) > rad;

  RegionPlot[
   And @@ (norm[{x, y}, #] & /@ Tuples[range]), {x, xmin, xmax}, {y, 
    ymin, ymax}, Evaluate@opts,
   AspectRatio -> Abs[ymax - ymin]/Abs[xmax - xmin]]]

example:

roundedRect[{{0, 5}, {0, 1}}, Frame -> False, RoundingRadius -> 0.4, 
 ColorFunction -> (Blend[{Red, Blue}, #2] &)]
share|improve this answer
add comment

Edit

One can use either an image-based (hence rasterized) or a vector-based (resolution-independent) approach to get the rounded corners. I'll first discuss the vector based solution, and then add a raster-based solution. Although Mr. Wizard already posted a raster-based approach, I think it can be improved.

Update

The function roundedGraphics is rewritten so that it contains both a vector and a bitmap option in a single command. The bitmap option isn't used until later.

Vector-based approach

This solves the question but can also be used more generally to put rounded corners on arbitrary objects:

Options[roundedGraphics] = {Background -> White, 
   ImageResolution -> Infinity};
roundedGraphics[g_, w_, h_, r_, opts : OptionsPattern[]] := Module[
  {bgColor = OptionValue[Background],
   resolution = OptionValue[ImageResolution],
   commonOptions = Sequence[
     PlotRange -> {{0, 1}, {0, 1}},
     ImageSize -> {w, h},
     AspectRatio -> Full],
   passepartout},
  passepartout = FilledCurve[
    {{BezierCurve[
         {{0, #1/h}, {0, 1 - #2/h}, {0, 1 - #2/h}, {0, 1}, {#2/w, 
           1}, {1 - #3/w, 1}, {1 - #3/w, 1}, {1, 1}, {1, 
           1 - #3/h}, {1, #4/h}, {1, #4/h}, {1, 0}, {1 - #4/w, 
           0}, {#1/w, 0}, {#1/w, 0}, {0, 0}, {0, #1/h}},
         SplineDegree -> 2
         ] & @@ Apply[PadRight[#, 4, Last[#]] &, {Flatten[{r}]}]},
     {Line[{{0, 0}, {0, 1}, {1, 1}, {1, 0}}]}}
    ];
  If[
     resolution < Infinity,
     SetAlphaChannel @@ Map[
       Rasterize[#, "Image",
         ImageResolution -> resolution] &,
       {#,
        Graphics[{FaceForm[Black], EdgeForm[Black], passepartout},
         Background -> White, commonOptions]}
       ],
     #
     ] &[
   Graphics[{Inset[g, {0, 0}, {Left, Bottom}, {1, 1}], 
     FaceForm[bgColor], EdgeForm[bgColor], passepartout}, 
    Background -> bgColor, commonOptions]]
  ]

For even more generality, I'm allowing each corner to have an individually different radius. But if you only specify a single radius, that number will be used for all corners.

The arguments w, h, and r are the image width, height and rounding radius in pixels.

To get the button with a gradient, I just have to take the "object" g that is passed to cropGraphics as a rectangle with the desired gradient. So let's just copy Mr. Wizard's choice of gradient here:

g1 = Graphics[{Polygon[{{0, 0}, {3, 0}, {3, 1}, {0, 1}}, 
    VertexColors -> {Red, Red, Blue, Blue}]}, ImagePadding -> 0, 
  PlotRangePadding -> 0]

Iv'e made sure the gradient rectangle doesn't have any whitespace around it. Now I'll apply the rounding to it:

roundedGraphics[Show[g1, AspectRatio -> Full], 400, 50, 20]

button

The point of my more complicated looking function is that you can use it with other objects:

im = Import["ExampleData/lena.tif"];
roundedGraphics[im, #1, #2, 10] & @@ ImageDimensions[im]

lena

g3 = Show[ExampleData[{"Geometry3D", "StanfordBunny"}], 
   ImageSize -> 360];

roundedGraphics[
 Show[g3, AspectRatio -> Full, Background -> Black], 400, 400, 20]

bunny

You may wonder what the purpose of the AspectRatio->Full statement in the last example is. To see what it does, change the width w of the roundedGraphics from 400 to 100. With AspectRatio->Full the inset object becomes stretchable. That's especially nice if you want to make a button from an image but the image dimensions don't match the button dimensions:

splash = ImageCrop[ExampleData[{"TestImage", "Splash"}], {400, 400}];    
roundedGraphics[Show[splash, AspectRatio -> Full], 400, 200, 20]

splashhigh

roundedGraphics[Show[splash, AspectRatio -> Full], 400, 100, 20]

splashLow

Here is an example that uses individual rounding radii (when given as a list, they start at the bottom left):

roundedGraphics[
 Show[splash, AspectRatio -> Full], 400, 100, {0, 20, 20, 0}]

RoundNotRound

This kind of arrangement can be useful when making tabs instead of buttons. Since the rounded boundary is defined by a Bezier curve, you can also invoke the interactive graphics editor to adjust the control points and re-shape the output (double-click on the masking border and highlight a point on the inner curve - the mouse pointer turns into a white dot when it's ready to select a curve point).

The masking shape that defines the rounded rectangle is white by default, but you can give it a different color by using the Background option.

The main ideas in this approach come from Yu-Sung Chang for the FilledCurve trick, and this answer regarding cropping of graphics for the Inset approach.

Raster-based approach

If you're going to choose the route via a bitmap representation of the button, then you may as well make better use of the features that a bitmap approach offers and that are hard to duplicate in the vector-based approach.

The obvious additional feature that one can add here is transparency, applied to the corners of the rounded button, so that the rounding also works when the image is superimposed on an arbitrary background.

I suggested that approach in a comment to this answer (which in its last part is identical to what Mr. Wizard used in his answer here):

g1 = 
 Graphics[{Polygon[{{0, 0}, {3, 0}, {3, 1}, {0, 1}}, 
    VertexColors -> {Red, Red, Blue, Blue}]}, PlotRangePadding -> None]

g2 = Graphics[{White, 
   Rectangle[{0, 0}, {3, 1}, RoundingRadius -> 0.5]}, 
  Background -> Black, PlotRangePadding -> None]

Mask

Now I define the button with rounded corners:

button = SetAlphaChannel[g1, g2];

To show the difference to ImageAdd, display the button in front of a background:

Show[button, Background -> Yellow]

button with background

This same method is also built into the function roundedGraphics. You invoke it simply by specifying the option ImageResolution - this tells it that you want a bitmap, and the alpha channel transparency is then automatically set. Here is another example with a gradient that explicitly uses bitmaps with the standard screen resolution:

bitmapButton = roundedGraphics[
  Graphics[
   Raster[Transpose@{(Range[256] - 1)/256},
    ColorFunction -> "NeonColors"],
   ImagePadding -> 0,
   PlotRangePadding -> 0,
   AspectRatio -> Full
   ], 300, 50, {10, 40, 10, 40}, ImageResolution -> 72]

button2

This button now has transparent corners, and by choosing a higher resolution you can get arbitrarily close to the quality of the vectorized version discussed earlier.

The output of roundedGraphics with the ImageResolution option isn't a Graphics object but an Image, so you have to use bitmap commands on it, as in this example:

ImageCompose[ExampleData[{"TestImage", "Tree"}], 
 ImageResize[bitmapButton, 200], {130, 130}]

images

share|improve this answer
    
Nice general answer @Jens. –  Mike Honeychurch Jun 9 '12 at 1:02
    
@MikeHoneychurch I've added an improved image-based approach since you appear to prefer that. It adds transparent corners so the button is independent of the background color. –  Jens Jun 9 '12 at 17:19
    
thanks @Jens ... –  Mike Honeychurch Jun 10 '12 at 1:27
add comment

Use ColorFunction along a single dimension for gradient and a smart analytic curve for boundary. You can easily control type of color gradient via ColorFunction.

RegionPlot[.7 x^8 + 80 y^8 < .3, {x, -2, 2}, {y, -2, 2}, 
 Frame -> False, Axes -> False, 
 ColorFunction -> Function[{x, y}, Hue[.3 y]]]

enter image description here

share|improve this answer
    
The Chang post does not deal with curved boundaries. I can fill a rectangle, star, you name it, with a gradient using the method in the question. My problem is how to get curved/rounded edges. –  Mike Honeychurch Jun 8 '12 at 8:20
    
Reducing the Thickness shows that this method produces apparent curved edges by "obliterating" the underlying graphic. As such the user is stuck with a very thick "frame" around the graphic. –  Mike Honeychurch Jun 8 '12 at 8:27
    
@MikeHoneychurch Sorry Mike, I just changed the whole thing before I saw your comment. –  Vitaliy Kaurov Jun 8 '12 at 8:31
    
while that is more concise than the original, the problem I have is that each time I want to change the shape I am going to get a headache trying to figure out the region equation. Surely there must be a way in Mma to start with a rounded rectangle and fill it with a gradient? –  Mike Honeychurch Jun 8 '12 at 8:35
add comment

The raster method I alluded to in a comment was requested.

g1 = Graphics[{
        Polygon[{{0, 0}, {3, 0}, {3, 1}, {0, 1}}, VertexColors -> {Red, Red, Blue, Blue}]
     }]

g2 = Graphics[{Rectangle[{0, 0}, {3, 1}, RoundingRadius -> 0.5]}]

ImageAdd[g1, g2]

Mathematica graphics

share|improve this answer
    
Ok I think I am going to have to accept that, unless e.g. @Heike comes on here with a solution. While I was wanting a non-rasterized solution this is too easy and concise to ignore. I'll leave it open for until tomorrow. –  Mike Honeychurch Jun 8 '12 at 8:41
    
To the person who downvoted may I please have an explanation? –  Mr.Wizard Jun 9 '12 at 1:34
2  
To whoever downvoted I specifically asked @Mr.Wizard to post a raster answer. –  Mike Honeychurch Jun 9 '12 at 6:34
    
You got a +1 from me, but I think I improved on your answer (see bottom of my answer). –  Jens Jun 9 '12 at 17:50
    
@Jens thanks for the vote. I don't have SetAlphaChannel but I suppose it works well. –  Mr.Wizard Jun 10 '12 at 0:07
show 1 more comment

Vitaliy had a great answer. I guess another way to do this is to simply make the curves using many lines:

enter image description here

In the following code, resolution is the number of lines used to make the curve and m is how big the corners are.

    resolution = 30;
    w = 2;
    h = 1;
    m = 0.1;

circlePoint[center_, radius_, radian_] := radius {Cos[radian], Sin[radian]} + center;

    max = Max[w, h]*m;
    pts1 = Sequence @@ 
       Table[circlePoint[{max, max}, max, r], {r, \[Pi], 
         3 \[Pi]/2, \[Pi]/2/resolution}];
    pts2 = Sequence @@ 
       Table[circlePoint[{w - max, max}, max, r], {r, 3 \[Pi]/2, 
         2 \[Pi], \[Pi]/2/resolution}];
    pts3 = Sequence @@ 
       Table[circlePoint[{w - max, h - max}, max, r], {r, 
         0, \[Pi]/2, \[Pi]/2/resolution}];
    pts4 = Sequence @@ 
       Table[circlePoint[{max, h - max}, max, 
         r], {r, \[Pi]/2, \[Pi], \[Pi]/2/resolution}];

    Graphics[{Polygon[{{0, max}, pts1, { max, 0}, {w - max, 0}, 
        pts2, {w, max}, {w, h - max}, pts3, {w - max, h}, {w - max, h }, 
        pts4, {0, h - max}}, 
       VertexColors -> 
        Join[Table[Red, {2 (resolution + 1) + 4}], 
         Table[Blue, {2 (resolution + 1) + 4}]]]}]
share|improve this answer
    
the resulting graphic looks very nice but you haven't defined circlePoint –  Mike Honeychurch Jun 8 '12 at 8:29
    
sorry, just added it –  M.R. Jun 8 '12 at 8:34
    
I assumed you wanted a non-rasterized method, but yes the image one is simpler... –  M.R. Jun 8 '12 at 9:05
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.