20
$\begingroup$

Preamble

First let me explain the why. You can skip this and go directly to the question if want to answer it right away. We had more than one discussion about how to produce nice looking 3d plots which can be exported to pdf or some other vector format. Some details why this is not really possible are explained in a post of mine in the question Exporting graphics to PDF - huge file.
To solve this issue, there is in my opinion a lot of work to do, since to make it really fancy one would have to reimplement the rendering and 2d-projection process which is done by Mathematica when it displays a Graphics3D.
A simple solution, which is maybe OK in a lot of cases, would be to separate the surface of a 3d plot from its box, labels and axes. Then one could create a high-resolution image of the surface and combine this again with the (still in vector format and infinitely high resolved) axes and labels. I showed in the linked post how to do this.
To make this working reliable one has to ensure, that it is possible to place the surface which is now a raster image at the exact same position where the original surface was before. To place the image Wolfram gave us Inset at hand which kind of does a good job, but when you look at it closely, there is some behavior I don't understand.

Question

Please consider the following two Plot3D where I tried to deny any padding, margin or whatsoever. One plot is rasterized and I fixed the ImageSize so that the outcome should be completely equal

s = {300, 300};
opts = {ImageSize -> s,
   PlotStyle -> None,
   PlotRangePadding -> None,
   ImagePadding -> None,
   ImageMargins -> None,
   Axes -> False,
   Boxed -> False};
gr1 = Plot3D[Sin[x + y^2], {x, -3, 3}, {y, -2, 2},
   PlotStyle -> None,
   Evaluate[Sequence @@ opts]
   ];
img1 =
  Rasterize[
   Plot3D[Sin[x + y^2], {x, -3, 3}, {y, -2, 2},
    PlotStyle -> Opacity[0.4],
    Evaluate[Sequence @@ opts]
    ],
   "Image",
   Background -> None,
   ImageSize -> s,
   RasterSize -> s
   ];

If you like, use Framed to put a frame around both graphics and try to see differences in placement or size. I could not find any. Creating now a combined Graphics3D with Inset where I used the AbsoluteOptions of gr1 for the graphics does show, that the inset graphics is not correctly placed. I could have used Show for the joining, the outcome would have been the same. Please note, that I initialized the variables {dx,dy,dz} to values which give a nice match on my machine. The image shows the zero position.

Manipulate[
 Graphics3D[{Inset[img1, {dx, dy, dz}], First[gr1]}, 
  AbsoluteOptions[gr1]],
 {{dx, -0.13`}, -.4, .4},
 {{dy, -0.14`}, -.4, .4},
 {{dz, -0.375`}, -.4, .4}
]

enter image description here

Can someone explain or solve this problem? Maybe someone has an idea where exactly these offsets come from.

Solution

It's worth looking at every answer below, because there are many valuable information, but the key point to the solution of my question was given by Heike who noticed, that the explicit setting of SphericalRegion influences the correct placement.

Nevertheless, working with Rasterize, ImageSize, ImageResolution, RasterSize and all the options influencing margins and paddings around graphics often leads to surprising results. So be warned.

With SphericalRegion->True in the plot commands, I could quite reliable create nice pdf's from various Graphics3D. Here the creation of a high-resolution pdf which has a filesize of 860Kb.

I have not included an image, because the png image comes not even close to how brilliant this looks in the pdf. Try it!

RasterizedGraphics3D[gr_Graphics3D, rastersize_Integer] := 
  Module[{dim = ImageDimensions[gr], factor},
   factor = rastersize/First[dim];
   Graphics3D[{Inset[
      GaussianFilter[
       Rasterize[Show[gr,
         Boxed -> False, Axes -> False, AbsoluteOptions[gr]],
        "Image", Background -> None, RasterSize -> factor*dim],
       .5 factor
       ],
      {0, 0, 0}, {Center, Center}, dim]},
    AbsoluteOptions[gr]]
   ];

grout = RasterizedGraphics3D[
  Plot3D[Im[ArcSin[(x + I y)^4]], {x, -2, 2}, {y, -2, 2}, 
   Mesh -> None, 
   PlotStyle -> 
    Directive[Yellow, Specularity[White, 20], Opacity[0.8]], 
   ExclusionsStyle -> {None, Red}, SphericalRegion -> True], 2400]
Export["tmp/tmp.pdf", grout, "AllowRasterization" -> False]

The GaussianFilter does basically antialiasing because it smooths the high-res raster-image version of the surface a bit. Thank's for all the answers and comments.

$\endgroup$
2
  • 1
    $\begingroup$ If you use Options[gr1] instead of AbsoluteOptions[gr1] and an offset of {dx, dy, dz} == {0,0,0} the two graphs align perfectly so there seems to be a discrepancy between Options[gr] and AbsoluteOptions[gr]. $\endgroup$
    – Heike
    Jun 6, 2012 at 8:17
  • $\begingroup$ Related: stackoverflow.com/q/6301676/618728 $\endgroup$
    – Mr.Wizard
    Jun 8, 2012 at 2:31

3 Answers 3

7
$\begingroup$

It seems that if you use Options[gr1] instead of AbsoluteOptions[gr1] and an offset of {dx, dy, dz} == {0,0,0} the two graphs align perfectly:

s = {300, 300};
opts = {ImageSize -> s, PlotStyle -> None, PlotRangePadding -> None, 
   ImagePadding -> None, ImageMargins -> None, Axes -> False, Boxed -> False};
gr1 = Plot3D[Sin[x + y^2], {x, -3, 3}, {y, -2, 2}, PlotStyle -> None, 
     Mesh -> None, Boxed -> True, ##] & @@ opts;
img1 = Rasterize[Plot3D[Sin[x + y^2], {x, -3, 3}, {y, -2, 2}, 
      PlotStyle -> Opacity[.5], ##] & @@ opts, "Image", 
   Background -> None, ImageSize -> s, RasterSize -> s];

Graphics3D[{Inset[img1, {0, 0, 0}], First[gr1]}, Options[gr1]]

Mathematica graphics

The next question is then what setting in AbsoluteOptions[gr1] causes the two graphs to misalign. After a bit of experimentation it turns out that the culprit is SphericalRegion. If this option is not set explicitly AbsoluteOptions[gr1, SphericalRegion] returns False by default but the actual setting used is the value of SphericalRegion in the $FrontEnd option Graphics3DBoxOptions. You can check this setting with

CurrentValue[$FrontEnd, {Graphics3DBoxOptions, SphericalRegion}]
$\endgroup$
7
  • 4
    $\begingroup$ This is still misaligned on my machine by a good deal of offset (Win7, 64bit, Mathematica 8.0.4). Is this behaviour platform specific? $\endgroup$ Jun 6, 2012 at 10:15
  • $\begingroup$ Same here, same platform $\endgroup$
    – Rojo
    Jun 6, 2012 at 10:32
  • $\begingroup$ I have the same misalignment problem with Windows. $\endgroup$ Jun 6, 2012 at 11:47
  • $\begingroup$ +1 although I have the mismatch here on Linux and MacOSX too. But maybe this is an important hint how we can track the issue. $\endgroup$
    – halirutan
    Jun 6, 2012 at 13:48
  • 1
    $\begingroup$ It works when one sets SphericalRegion->True explicitely in opts. I'll investigate this further. $\endgroup$
    – halirutan
    Jun 7, 2012 at 0:47
5
$\begingroup$

The reason for this misplacement is that due to the perspective, the center of the surface is not at the center of the image. You can see that quite nicely if you manipulate the image to have an X cross, and the 3D graph to have a sphere at the origin (I've also slightly darkened the image to make it more obvious):

s1 = 300;s2 = 300;s = {s1, s2};
opts = {ImageSize->s, PlotStyle->None, PlotRangePadding->None, ImagePadding->None,
        ImageMargins->None, Axes->False, Boxed->False};
gr1 = Show[Plot3D[Sin[x+y^2], {x, -3, 3}, {y, -2, 2},
                  PlotStyle->None, Mesh->None, Boxed->True, ##]&@@opts,
           Graphics3D[{Green,Sphere[{0,0,0},0.1]}]];
img1 = Show[ImageAdd[#, 0.2]&@
              Rasterize[Show[Plot3D[Sin[x+y^2], {x, -3, 3}, {y, -2, 2},
                                    PlotStyle->Opacity[.5], ##]&@@opts,
                             Graphics3D[{Red, Sphere[{0,0,0},0.1]}]],
                        "Image", Background->None, ImageSize->s, RasterSize->s],
            Graphics[{Red, Line[{{0, 0}, {s1, s2}}], Line[{{0, s1}, {s2, 0}}]}]];

Graphics3D[{Inset[img1,{0,0,0}],First[gr1]},Options[gr1]]

The center of the red X cross (which also is the center of the image) is at the green sphere (marking the {0,0,0} of the actual plot) but the red sphere (the place in the 2D image the {0,0,0} coordinates ended up to be shown) is not.

However, Heike's observation that it is related to SphericalRegion is true, because if you add ShphericalRegion->True to opts, the plots sit on top of each other perfectly. Note that this is not because of some mismatch (giving SphericalRegion->False explicitly does not give matching graphs), but because SphericalRegion->True actually puts the center of the 3D image in the center of the 2D image (which makes sense because a sphere has the same distance to the border in all directions). However note that {0,0,0} for the inset coordinates will only work if {0,0,0} is really at the center (use {x,-3,1} instead of {x,-3,3} for the plot to see the effect). Scaled@{0.5,0.5,0.5} should always work, however.

$\endgroup$
5
$\begingroup$

The following works perfectly:

Graphics[{Inset[img1, {0, 0}], Inset[gr1, {0, 0}]}]

To see that it really works, just add Mesh -> False to the definition of img1.

Since the stated motivation is to export to PDF, it may sometimes be unnecessary to fit an Inset into a Graphics3D object (even though it was nice to see that it could be done in principle). That's why I proceeded the other way around and put the 3d graphics into a 2D object instead.

Edit As Heike points out, it's important to keep in mind that the mesh 2D lines aren't hidden when in the background. For that reason, if one wants to use my approach one would have to find another way to mask hidden lines.

Edit 2

The problem with my approach is that hidden mesh lines will be visible. The problem with the opposite approach (placing a rasterized image into the 3D scene) is exactly the opposite but no less annoying: if the surface is opaque (Opacity[1]), then mesh lines that are drawn as vector lines can end up on either side of the surface, depending on perspectve. To see this, try the following modification of Heike's (and celtschk's) solution:

s1 = 300; s2 = 300; s = {s1, s2};
opts = {SphericalRegion -> True, ImageSize -> s, PlotStyle -> None, 
   PlotRangePadding -> None, ImagePadding -> None, 
   ImageMargins -> None, Axes -> False, Boxed -> False, 
   ViewAngle -> .3};
gr1 = Show[
   Plot3D[Sin[x + y^2], {x, -3, 3}, {y, -2, 2}, PlotStyle -> None, 
      Mesh -> True, Boxed -> True, ##] & @@ opts];
img1 = Show[
   Rasterize[
    Show[Plot3D[Sin[x + y^2], {x, -3, 3}, {y, -2, 2}, Mesh -> False, 
        PlotStyle -> Opacity[1], ##] & @@ opts, 
     Graphics3D[{Red, Sphere[{0, 0, 0}, 0.1]}]], "Image", 
    Background -> None, ImageSize -> s, RasterSize -> s]];

Graphics3D[{Inset[img1, {0, 0, 0}], First[gr1]}, Options[gr1]]

wrongmesh

Therefore, using an inset in Graphics3D to achieve a surface mesh doesn't look very robust to me. Not to mention the fact that the result doesn't necessarily look any better than the rasterized version because the mesh lines are zig-zaggy, too... I keep coming back to rasterization as the best option.

Edit 3 After experimenting with this some more, I can't make Heike's approach work reliably with mesh lines on the surface - they're showing in the wrong places and hidden in other wrong places.

This means that mesh lines have to remain part of the rasterized portion of the plot if they are desired at all. What is left is the alignment of raster image and vectorized decorations such as the box and frame. My initial approach leaves all vectorized lines visible, but it has advantages too: In contrast to the other solution, my approach does not have to assume that the Graphics3D is prepared using SphericalRegion->True. I can also add 3D axes and tick marks with a non-zero ImagePadding (anything other than Automatic seems to work fine):

s = {600, 600};
opts = {ImageSize -> s, PlotStyle -> None, PlotRangePadding -> None, 
   ImagePadding -> 50, ImageMargins -> None};
gr1 = Plot3D[Sin[x + y^2], {x, -3, 3}, {y, -2, 2}, Mesh -> False, 
   Evaluate[Sequence @@ opts]];
img1 = Rasterize[
   Plot3D[Sin[x + y^2], {x, -3, 3}, {y, -2, 2}, Mesh -> True, 
    PlotStyle -> Opacity[1], BoxStyle -> Opacity[0], 
    AxesStyle -> Opacity[0], Evaluate[Sequence @@ opts]], "Image", 
   ImageSize -> s, RasterSize -> s];

Graphics[{Inset[img1, {0, 0}], Inset[gr1, {0, 0}]}, 
 PlotRange -> {{-.25, .25}, {-.25, .25}}, ImageSize -> 650]

Export["plot.pdf", %]

Here is the PDF:

PDF screenshot

Edit 4: Comparison to rasterized export

Using halirutan's example, let's see what a rasterized 3d plot looks like, using the trick I describe here:

SetOptions[Plot3D, 
  Prolog -> {{EdgeForm[], Texture[{{{0, 0, 0, 0}}}], 
     Polygon[#, VertexTextureCoordinates -> #] &[{{0, 0}, {1, 0}, {1, 
        1}}]}}];

p = Plot3D[Im[ArcSin[(x + I y)^4]], {x, -2, 2}, {y, -2, 2}, 
  Mesh -> None, 
  PlotStyle -> 
   Directive[Yellow, Specularity[White, 20], Opacity[0.8]], 
  ExclusionsStyle -> {None, Red}]

Export["plot3d.pdf", Magnify@p]

rasterized plot

The PDF is for all practical purposes (mine at least) as good as a PDF, and takes only 400 kB - half of the files size of halirutan's partially rasterized PDF with the same plot. There are no restrictions as to how the 3D plot is prepared (SphericalRegion or not, etc.), and you can even rotate the plot before exporting, e.g., by doing a copy and paste like so:

usage example

$\endgroup$
15
  • 2
    $\begingroup$ The problem with using 2D insets for both the (vectorized) axes and (rasterized) surface is that the axes will appear either completely on top or completely behind the surface and not partially in front and partially behind like in an actual 3D plot. $\endgroup$
    – Heike
    Jun 6, 2012 at 8:31
  • $\begingroup$ My conclusion from these complications is that currently I would in practice always go back to my answer in Exporting graphics to PDF - huge file and export everything as high-res bitmap. It's so much easier. $\endgroup$
    – Jens
    Jun 6, 2012 at 15:14
  • $\begingroup$ @Jens, my goal was not to put a vector-mesh over a raster surface! It was only to show the offset when joining the two plots. I just wanted to place the rasterized surface into a vector axes-box. $\endgroup$
    – halirutan
    Jun 7, 2012 at 0:51
  • $\begingroup$ @halirutan I just think that there's something fishy going on with Inset in Graphics3D. For example, I also can't seem to get the graphics to rotate interactively. I tried various things to make it work more robustly - e.g., literally replaced Inset by Text. It gets the same result as Heike, and still doesn't align properly without SphericalRegion or rotate interactively. So I don't quite trust that approach... $\endgroup$
    – Jens
    Jun 7, 2012 at 2:47
  • $\begingroup$ I cannot even select this graphic here on my Linux box, but that does not matter. I want small and excellent pdf's. If this is the price, so be it. Afaik Text and Inset is kind of the same. And I have to set SphericalRegion explicitly in the plot too, but look at my update in the question and try the code. It really looks very nice. $\endgroup$
    – halirutan
    Jun 7, 2012 at 3:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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