How can I create a rectangular graphic with curved edges?
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]
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]
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]
g3 = Show[ExampleData[{"Geometry3D", "StanfordBunny"}],
ImageSize -> 360];
roundedGraphics[
Show[g3, AspectRatio -> Full, Background -> Black], 400, 400, 20]
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]
roundedGraphics[Show[splash, AspectRatio -> Full], 400, 100, 20]
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}]
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]
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]
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]
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}]
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] &)]
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] &)]