A bug report has been generated on this issue, but I don't believe any resources are being expended on it at the present time (note that the more user reports are received on an issue, the more likely it is that the issue will get resources). As a workaround, it is possible to hook into the internal code that is used for Graphics3D
primitives. The key idea is that TransformedRegion
knows how to convert 2D graphics primitives. Here are a couple examples:
TransformedRegion[Rectangle[], RotationTransform[Pi/4, {.5, .5}]]
TransformedRegion[Line[{{0,0}, {1,1}, {1,2}}], ScalingTransform[{2,3}, {1,1}]]
Parallelogram[{0.5, -0.207107}, {{-0.707107, 0.707107}, {0.707107, 0.707107}}]
Line[{{-1, -2}, {1, 1}, {1, 4}}]
So, what is needed is a way to convert objects like Rotate[Rectangle[], Pi/4]
into the equivalent GeometricTransformation
/TransformedRegion
version. This is not as simple as it might seem because unless otherwise specified, Rotate
and Scale
keep some point of the body fixed. Hence the following two Graphics
outputs are different:
GraphicsRow[{
Graphics[{Rotate[Rectangle[], Pi/4]}, Axes->True, PlotRange->{{-1,1.5}, {-.5, 1.5}}],
Graphics[{TransformedRegion[Rectangle[], RotationTransform[Pi/4]]}, Axes->True,PlotRange->{{-1,1.5}, {-.5, 1.5}}]
}]
The Rotate
object keeps the center of the rectangle fixed, while the RotationTransform
keeps the origin fixed. One needs to determine the coordinate that Rotate
/Scale
fix, and then feed that to the appropriate transform. The default case is to use the center of the bounding rectangle, which can be determined with the following function:
regionCenter[g_] := Mean /@ RegionBounds[g]
For example:
regionCenter[Rectangle[]]
regionCenter[Triangle[]]
regionCenter[Circle[{0,0}, {1, 2}, {0, Pi}]]
{1/2, 1/2}
{1/2, 1/2}
{0, 1}
Note that this is different from RegionCentroid
! The other case that must be handled is when symbolic position specifications like Left
and Bottom
are used. Here is a function that converts these symbolic based position specifications into actual coordinates:
absolutePosition[g_, {h:(Left|Center|Right), v:(Top|Center|Bottom)}] := Module[
{hrange,vrange},
{hrange, vrange} = RegionBounds[g][[;;2]];
{
Replace[h, {Left->Min, Center->Mean, Right->Max}][hrange],
Replace[v, {Bottom->Min, Center->Mean, Top->Max}][vrange]
}
]
absolutePosition[g_, spec_]:=spec
Note that the regionCenter
function is just a special case where the second argument is {Center, Center}
. Here is an example:
Graphics[Triangle[], Axes->True, ImageSize->100]
absolutePosition[Triangle[], {Right, Top}]
{1, 1}
And yes, the fixed point of rotation with a position spec of {Right, Top}
is indeed {1, 1}
:
Graphics[
{
Red, PointSize[Large], Point[{1,1}],
Green, Triangle[],
Blue, Rotate[Triangle[], Pi/2, {Right, Top}]
},
Axes->True, PlotRange->{{0, 2}, {0, 1}}
]
Finally, GeometricTransformation
evaluates to a form where the transform has been converted to the equivalent matrix version, but this form doesn't work in TransformedRegion
:
GeometricTransformation[Rectangle[], TranslationTransform[{1,1}]]
GeometricTransformation[Rectangle[{0, 0}], {{{1, 0}, {0, 1}}, {1, 1}}]
TransformedRegion[Rectangle[], {{{1,0},{0,1}},{1,1}}]
TransformedRegion::vfunc: {{{1,0},{0,1}},{1,1}} evaluated at a list of length 2 should give a non-empty list.
TransformedRegion[Rectangle[{0, 0}], {{{1, 0}, {0, 1}}, {1, 1}}]
One must convert the matrix representation back into a transform representation:
TransformedRegion[Rectangle[], AffineTransform[{{{1,0},{0,1}},{1,1}}]]
Rectangle[{1, 1}, {2, 2}]
The internal code that I will hook into is System`Private`InternalNormal
, which has a conditioned DownValues
to convert 3D graphics. When that DownValues
fails for 2D graphics, the new DownValues
that I add will be used. Here is the complete code:
NormalizeGraphics[g_] := Internal`InheritedBlock[{System`Private`InternalNormal},
Unprotect[System`Private`InternalNormal];
System`Private`InternalNormal[
gr:_Rotate|_Translate|_Scale|_GeometricTransformation,
_
] := Module[{tmp = Quiet[transform2D[gr], TransformedRegion::reg]},
tmp /; Head[tmp]=!=TransformedRegion
];
Normal[g, {Rotate, Scale, Translate, GeometricTransformation}]
]
transform2D[Rotate[g_, r_, p___]] := TransformedRegion[
g,
RotationTransform[r, absolutePosition[g, p]]
]
transform2D[Translate[g_, t_]] := TransformedRegion[
g,
TranslationTransform[t]
]
transform2D[Scale[g_, s_, p___]] := TransformedRegion[
g,
ScalingTransform[s, absolutePosition[g, p]]
]
transform2D[GeometricTransformation[g_, tf_]] := TransformedRegion[
g,
tf /. Except[_TransformationRegion] :> AffineTransform[tf]
]
absolutePosition[g_] := absolutePosition[g, {Center, Center}]
absolutePosition[g_, {h:(Left|Center|Right), v:(Top|Center|Bottom)}] := Module[
{hrange,vrange},
{hrange, vrange} = RegionBounds[g][[;;2]];
{
Replace[h, {Left->Min, Center->Mean,Right->Max}][hrange],
Replace[v,{Bottom->Min,Center->Mean,Top->Max}][vrange]
}
]
absolutePosition[g_, spec_]:=spec
And here is an example of NormalizeGraphics
in action:
g = Graphics[{
GeometricTransformation[Circle[], TranslationTransform[{-1,1}]],
Rotate[Rectangle[], Pi/4],
Scale[Triangle[], {1,2}]
}]
normal = NormalizeGraphics @ g
normal //InputForm
Graphics[{Circle[{-1, 1}, 1], Polygon[{{1/2, (1 - Sqrt[2])/2},
{1/2 - 1/Sqrt[2], 1/Sqrt[2] + (1 - Sqrt[2])/2}, {1/2, Sqrt[2] + (1 - Sqrt[2])/2},
{1/2 + 1/Sqrt[2], 1/Sqrt[2] + (1 - Sqrt[2])/2}}], Triangle[{{0, -1/2}, {1, -1/2}, {0, 3/2}}]}]
Note the absence of Rotate
, Scale
and GeometricTransformation
!
Finally, the example from the OP:
NormalizeGraphics @ Graphics[g]
% //InputForm
Graphics[Polygon[{{0.3638212597003222, -0.34572544753218604}, {0.35420478721328924,
-0.34662116780165864}, {0.3358982454760883, -0.34574632062896415}, {0.3271631432644535,
-0.34409078711013413}, {0.3104166937514485, -0.3386310850158512}, {0.2944851409139063,
-0.3306899825905281}, {0.27172170451125205, -0.3151322501610421}, {0.2497785439953863,
-0.29657963026179973}, {0.20592127070481253, -0.25670155991424687}, {0.18279126797050033,
-0.23848202539603736}, {0.15804976120376862, -0.22347943526827302}, {0.14035992359815785,
-0.2160317926667024}, {0.13108880542481546, -0.21324674749600434}, {-0.05210659373892817,
-0.18211233165697607}, {-0.2353019929026718, -0.15097791581794784}, {0.4052655948346296,
-0.2727349143130483}, {0.4081898037937953, -0.29027196246219794}, {0.40861048083253815,
-0.3034238823286141}, {0.40713629246244065, -0.31474184134416383}, {0.40355234983267335,
-0.3242802173337974}, {0.39935059755311264, -0.3302988596255873}, {0.39155750867161554,
-0.3368538119611556}, {0.38395211028863324, -0.3406973360457382}, {0.3747369127095167,
-0.3436469010211146}, {0.3638212597003222, -0.34572544753218604}}]]
Normal
will give you the coordinates, butPolygon[...][[1]]
will. $\endgroup$Normal
will perform the transformations explicitly:" $\endgroup$