/*========================================================================= Program: Visualization Toolkit Module: vtkGL2PSUtilities.cxx Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or http://www.kitware.com/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ #include "vtkGL2PSUtilities.h" #include "vtkIntArray.h" #include "vtkFloatArray.h" #include "vtkMath.h" #include "vtkMatrix4x4.h" #include "vtkNew.h" #include "vtkObjectFactory.h" #include "vtkOpenGLGL2PSHelper.h" #include "vtkPath.h" #include "vtkRenderWindow.h" #include "vtkStdString.h" #include "vtkTextProperty.h" #include "vtkTextRenderer.h" #include "vtk_gl2ps.h" #include #include #include vtkStandardNewMacro(vtkGL2PSUtilities) // Initialize static members vtkRenderWindow *vtkGL2PSUtilities::RenderWindow = nullptr; bool vtkGL2PSUtilities::TextAsPath = false; float vtkGL2PSUtilities::PointSizeFactor = 5.f / 7.f; float vtkGL2PSUtilities::LineWidthFactor = 5.f / 7.f; namespace { bool GetMetrics(vtkTextProperty* tprop, const char* str, vtkTextRenderer::Metrics& m) { int dpi = vtkGL2PSUtilities::GetRenderWindow()->GetDPI(); vtkTextRenderer* tren = vtkTextRenderer::GetInstance(); if (! tren) { return false; } vtkNew tpropTmp; tpropTmp->ShallowCopy(tprop); tpropTmp->SetOrientation(0.); if(! tren->GetMetrics(tpropTmp, str, m, dpi)) { return false; } return true; } // replace \n with space as PS treats it as a space but PDF just removes them. // we also need this so that we get the correct bounding box for PDFs // considering that we do not address multi-line strings yet. void GetSpaceStr(const char* str, vtkStdString* spaceStr) { *spaceStr = str; std::string::size_type eolPos = 0; while ((eolPos = spaceStr->find('\n', eolPos)) != std::string::npos) { spaceStr->replace(eolPos, 1, 1, ' '); ++eolPos; } } /** * Computes the bottom left corner 'blpos' and a string with \n replaced * by space 'spaceStr' for the string 'str' with properties 'tprop' * and the anchor 'pos'. * * We need this because PDF does not support text alignment. * 'spaceStr' is needed because postscript and PDF do not support * multiline text and we don't implement it yet for TextAsPath false. */ bool ComputeBottomLeft(vtkTextProperty* tprop, vtkTuple bbox, double pos[3], double blpos[3]) { std::copy(pos, pos + 3, blpos); // Postscript and PDF do not support multiline text - this is not // implemented yet for TextAsPath == 0 implement alignment for PDF if (gl2psGetFileFormat () == GL2PS_PDF && ! vtkGL2PSUtilities::GetTextAsPath() && (tprop->GetJustification() != VTK_TEXT_LEFT || tprop->GetVerticalJustification() != VTK_TEXT_BOTTOM)) { double width = bbox[1] - bbox[0] + 1; double height = bbox[3] - bbox[2] + 1; switch(tprop->GetJustification()) { case VTK_TEXT_CENTERED: blpos[0] -= width / 2; break; case VTK_TEXT_RIGHT: blpos[0] -= width; break; } switch(tprop->GetVerticalJustification()) { case VTK_TEXT_CENTERED: blpos[1] -= height / 2; break; case VTK_TEXT_TOP: blpos[1] -= height; break; } blpos[2] = 0; return true; } else { return false; } } } void vtkGL2PSUtilities::DrawString(const char *str, vtkTextProperty *tprop, double pos[3], double) { if (!str) { return; } vtkTextRenderer *tren(vtkTextRenderer::GetInstance()); if (tren == nullptr) { vtkNew dummy; vtkErrorWithObjectMacro(dummy, <<"vtkTextRenderer unavailable."); return; } int dpi = vtkGL2PSUtilities::RenderWindow->GetDPI(); // Draw the background if needed: if (tprop->GetBackgroundOpacity() > 0.) { vtkTextRenderer::Metrics metrics; if (tren->GetMetrics(tprop, str, metrics, dpi)) { double bgPos[4] = { pos[0], pos[1], pos[2], 1. }; vtkGL2PSUtilities::ProjectPoint(bgPos); bgPos[2] += 1e-6; double bgVerts[16]; bgVerts[0] = bgPos[0] + static_cast(metrics.TopLeft[0]); bgVerts[1] = bgPos[1] + static_cast(metrics.TopLeft[1]); bgVerts[2] = bgPos[2]; bgVerts[3] = bgPos[3]; bgVerts[4] = bgPos[0] + static_cast(metrics.BottomLeft[0]); bgVerts[5] = bgPos[1] + static_cast(metrics.BottomLeft[1]); bgVerts[6] = bgPos[2]; bgVerts[7] = bgPos[3]; bgVerts[8] = bgPos[0] + static_cast(metrics.BottomRight[0]); bgVerts[9] = bgPos[1] + static_cast(metrics.BottomRight[1]); bgVerts[10] = bgPos[2]; bgVerts[11] = bgPos[3]; bgVerts[12] = bgPos[0] + static_cast(metrics.TopRight[0]); bgVerts[13] = bgPos[1] + static_cast(metrics.TopRight[1]); bgVerts[14] = bgPos[2]; bgVerts[15] = bgPos[3]; vtkGL2PSUtilities::UnprojectPoints(bgVerts, 4); glDisable(GL_LIGHTING); glDisableClientState(GL_COLOR_ARRAY); glEnableClientState(GL_VERTEX_ARRAY); glColor4d(tprop->GetBackgroundColor()[0], tprop->GetBackgroundColor()[1], tprop->GetBackgroundColor()[2], tprop->GetBackgroundOpacity()); glVertexPointer(3, GL_DOUBLE, 4 * sizeof(double), bgVerts); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } } bool isMath = tren->DetectBackend(str) == vtkTextRenderer::MathText; if (!isMath && !vtkGL2PSUtilities::TextAsPath) { const char *fontname = vtkGL2PSUtilities::TextPropertyToPSFontName(tprop); GLint align = static_cast( vtkGL2PSUtilities::TextPropertyToGL2PSAlignment(tprop)); GLfloat angle = static_cast(tprop->GetOrientation()); // GL2PS assumes 72 DPI, so we'll have to adjust the font size: GLint fontSize = static_cast(tprop->GetFontSize() * (dpi / 72.)); GL2PSrgba rgba; double rgbad[3]; tprop->GetColor(rgbad[0], rgbad[1], rgbad[2]); rgba[0] = static_cast(rgbad[0]); rgba[1] = static_cast(rgbad[1]); rgba[2] = static_cast(rgbad[2]); rgba[3] = tprop->GetOpacity(); glRasterPos3dv(pos); // get pos in window coordinates GLboolean valid; glGetBooleanv(GL_CURRENT_RASTER_POSITION_VALID, &valid); if(GL_FALSE == valid) { // we cannot draw the text. return; } double posWin[4]; glGetDoublev(GL_CURRENT_RASTER_POSITION, posWin); // draw text by passing the bottom left corner as PDF does not support // alignment. double blpos[3]; vtkStdString spaceStr; // compute the bounding box and the string without \n vtkTextRenderer::Metrics m; ::GetSpaceStr(str, &spaceStr); if (! ::GetMetrics(tprop, spaceStr.c_str(), m)) { // we cannot draw the text return; } if (::ComputeBottomLeft(tprop, m.BoundingBox, posWin, blpos)) { // move the bottom left corner to the baseline as this is how PDF // draws text blpos[1] -= m.Descent[1]; gl2psTextOptColorBL(spaceStr.c_str(), fontname, fontSize, align, angle, rgba, blpos[0], blpos[1]); } else { // move the bottom left corner to the baseline as this // how PDF draws text. // See: // 10.070 How do I draw glBitmap() or glDrawPixels() primitives that // have an initial glRasterPos() outside the window's left or bottom edge? // https://www.opengl.org/archives/resources/faq/technical/clipping.htm#0070 glBitmap(0, 0, 0, 0, 0, - m.Descent[1], NULL); gl2psTextOptColor(str, fontname, fontSize, align, angle, rgba); } } else { // Render the string to a path and then draw it to GL2PS: vtkNew path; tren->StringToPath(tprop, str, path, dpi); // Get color double rgbd[3]; tprop->GetColor(rgbd[0], rgbd[1], rgbd[2]); unsigned char rgba[4] = { static_cast(rgbd[0]*255), static_cast(rgbd[1]*255), static_cast(rgbd[2]*255), static_cast(tprop->GetOpacity()*255)}; double devicePos[4] = { pos[0], pos[1], pos[2], 1. }; vtkGL2PSUtilities::ProjectPoint(devicePos); vtkGL2PSUtilities::DrawPath(path, pos, devicePos, rgba, NULL, 0.0, -1.f, (std::string("Pathified string: ") + str).c_str()); } } const char *vtkGL2PSUtilities::TextPropertyToPSFontName(vtkTextProperty *tprop) { bool bold = tprop->GetBold() != 0; bool italic = tprop->GetItalic() != 0; switch (tprop->GetFontFamily()) { case VTK_ARIAL: { if (!bold && !italic) { return "Helvetica"; } else if (bold && italic) { return "Helvetica-BoldItalic"; } else if (bold) { return "Helvetica-Bold"; } else if (italic) { return "Helvetica-Italic"; } } break; case VTK_TIMES: { if (!bold && !italic) { return "Times-Roman"; } else if (bold && italic) { return "Times-BoldOblique"; } else if (bold) { return "Times-Bold"; } else if (italic) { return "Times-Oblique"; } } break; case VTK_COURIER: { if (!bold && !italic) { return "Courier"; } else if (bold && italic) { return "Courier-BoldOblique"; } else if (bold) { return "Courier-Bold"; } else if (italic) { return "Courier-Oblique"; } } break; case VTK_UNKNOWN_FONT: default: break; } return "Helvetica"; } int vtkGL2PSUtilities::TextPropertyToGL2PSAlignment(vtkTextProperty *tprop) { switch (tprop->GetJustification()) { case VTK_TEXT_LEFT: switch (tprop->GetVerticalJustification()) { case VTK_TEXT_TOP: { return GL2PS_TEXT_TL; } case VTK_TEXT_CENTERED: { return GL2PS_TEXT_CL; } case VTK_TEXT_BOTTOM: { return GL2PS_TEXT_BL; } } break; case VTK_TEXT_CENTERED: switch (tprop->GetVerticalJustification()) { case VTK_TEXT_TOP: { return GL2PS_TEXT_T; } case VTK_TEXT_CENTERED: { return GL2PS_TEXT_C; } case VTK_TEXT_BOTTOM: { return GL2PS_TEXT_B; } } break; case VTK_TEXT_RIGHT: switch (tprop->GetVerticalJustification()) { case VTK_TEXT_TOP: { return GL2PS_TEXT_TR; } case VTK_TEXT_CENTERED: { return GL2PS_TEXT_CR; } case VTK_TEXT_BOTTOM: { return GL2PS_TEXT_BR; } } default: break; } return GL2PS_TEXT_BL; } void vtkGL2PSUtilities::Draw3DPath(vtkPath *path, vtkMatrix4x4 *actorMatrix, double rasterPos[3], unsigned char actorColor[4], const char *label) { double translation[2] = {0.0, 0.0}; vtkNew projPath; projPath->DeepCopy(path); vtkGL2PSUtilities::ProjectPoints(projPath->GetPoints(), actorMatrix); vtkGL2PSUtilities::DrawPath(projPath, rasterPos, translation, actorColor, NULL, 0.0, -1.f, label); } void vtkGL2PSUtilities::DrawPath(vtkPath *path, double rasterPos[3], double windowPos[2], unsigned char rgba[4], double scale[2], double rotateAngle, float strokeWidth, const char *label) { // Replace newlines in label -- these will throw off the comments. std::string l(label ? label : ""); size_t idx = 0; while ((idx = l.find('\n', idx)) != std::string::npos) { l.replace(idx, 1, "\\n"); } switch (gl2psGetFileFormat()) { case GL2PS_PS: case GL2PS_EPS: vtkGL2PSUtilities::DrawPathPS(path, rasterPos, windowPos, rgba, scale, rotateAngle, strokeWidth, l.c_str()); break; case GL2PS_SVG: vtkGL2PSUtilities::DrawPathSVG(path, rasterPos, windowPos, rgba, scale, rotateAngle, strokeWidth, l.c_str()); break; case GL2PS_PDF: vtkGL2PSUtilities::DrawPathPDF(path, rasterPos, windowPos, rgba, scale, rotateAngle, strokeWidth, l.c_str()); break; default: break; } } void vtkGL2PSUtilities::StartExport() { // This is nasty -- the tokens are used in the feedback buffer to tell GL2PS // about stippling or when the linewidth/pointsize changes. These are the // values defined in gl2ps.c as of v1.3.8. If these values change (doubtful) // we'll need to detect the gl2ps version and set the values per version. // // We set these in the helper class to fake the GL2PS functions that inject // the tokens into the feedback buffer to avoid making vtkRenderingOpenGL // depend on gl2ps. vtkOpenGLGL2PSHelper::StippleBeginToken = 5.f; // GL2PS_BEGIN_STIPPLE_TOKEN vtkOpenGLGL2PSHelper::StippleEndToken = 6.f; // GL2PS_END_STIPPLE_TOKEN vtkOpenGLGL2PSHelper::PointSizeToken = 7.f; // GL2PS_POINT_SIZE_TOKEN vtkOpenGLGL2PSHelper::LineWidthToken = 8.f; // GL2PS_LINE_WIDTH_TOKEN // These are used to scale the points and lines: vtkOpenGLGL2PSHelper::PointSizeFactor = vtkGL2PSUtilities::PointSizeFactor; vtkOpenGLGL2PSHelper::LineWidthFactor = vtkGL2PSUtilities::LineWidthFactor; // Enable the code paths that interact with the feedback buffer: vtkOpenGLGL2PSHelper::InGL2PSRender = true; } void vtkGL2PSUtilities::FinishExport() { vtkOpenGLGL2PSHelper::InGL2PSRender = false; } void vtkGL2PSUtilities::DrawPathPS(vtkPath *path, double rasterPos[3], double windowPos[2], unsigned char rgba[4], double scale[2], double rotateAngle, float strokeWidth, const char *label) { vtkFloatArray *points = vtkArrayDownCast(path->GetPoints()->GetData()); vtkIntArray *codes = path->GetCodes(); if (points->GetNumberOfTuples() != codes->GetNumberOfTuples()) { return; } std::stringstream out; out.setf(std::ios_base::left | std::ios_base::fixed); const int floatPrec = 2; const int floatWidth = 6; out.precision(floatPrec); out.width(floatWidth); float curveto[3][2]; float curX = 0; float curY = 0; float *pt = points->GetPointer(0); int *code = codes->GetPointer(0); // These are only used in an assertion, ifdef silences warning on non-debug // builds #ifndef NDEBUG float *ptBegin = pt; int *codeBegin = code; #endif int *codeEnd = code + codes->GetNumberOfTuples(); if (label != nullptr && label[0] != '\0') { out << "% " << label << endl; } out << "gsave" << endl; out << "initmatrix" << endl; out << windowPos[0] << " " << windowPos[1] << " translate" << endl; if (scale) { out << scale[0] << " " << scale[1] << " scale" << endl; } out << rotateAngle << " rotate" << endl; out << "newpath" << endl; while (code < codeEnd) { assert(pt - ptBegin == (code - codeBegin) * 3); switch (static_cast(*code)) { case vtkPath::MOVE_TO: curX = *pt; curY = *(++pt); ++pt; // Flush z out << curX << " " << curY << " moveto\n"; break; case vtkPath::LINE_TO: curX = *pt; curY = *(++pt); ++pt; // Flush z out << curX << " " << curY << " lineto\n"; break; case vtkPath::CONIC_CURVE: // Postscript doesn't support conic curves -- elevate order to cubic: { // Next point should be a CONIC_CURVE as well code += 1; const float &conicX = *pt; const float &conicY = *(++pt); ++pt; // Flush z const float &nextX = *(++pt); const float &nextY = *(++pt); ++pt; // Flush z // Perform degree elevation: curveto[0][0] = (1.0/3.0)*curX + (2.0/3.0)*conicX; curveto[0][1] = (1.0/3.0)*curY + (2.0/3.0)*conicY; curveto[1][0] = (2.0/3.0)*conicX + (1.0/3.0)*nextX; curveto[1][1] = (2.0/3.0)*conicY + (1.0/3.0)*nextY; curveto[2][0] = curX = nextX; curveto[2][1] = curY = nextY; out << curveto[0][0] << " " << curveto[0][1] << endl << curveto[1][0] << " " << curveto[1][1] << endl << curveto[2][0] << " " << curveto[2][1] << " curveto" << endl; } break; case vtkPath::CUBIC_CURVE: { // Next two points should be CUBIC_CURVEs as well code += 2; curveto[0][0] = *pt; curveto[0][1] = *(++pt); ++pt; curveto[1][0] = *(++pt); curveto[1][1] = *(++pt); ++pt; curveto[2][0] = curX = *(++pt); curveto[2][1] = curY = *(++pt); ++pt; out << curveto[0][0] << " " << curveto[0][1] << endl << curveto[1][0] << " " << curveto[1][1] << endl << curveto[2][0] << " " << curveto[2][1] << " curveto" << endl; } break; default: out << "% Unrecognized control code: " << *code << endl; pt +=2; break; } ++code; ++pt; } out << static_cast(rgba[0])/255.f << " " << static_cast(rgba[1])/255.f << " " << static_cast(rgba[2])/255.f << " setrgbcolor" << endl; if (strokeWidth > 1e-5) { out << strokeWidth << " setlinewidth\nstroke" << endl; } else { out << "fill" << endl; } out << "grestore" << endl; glRasterPos3dv(rasterPos); gl2psSpecial(gl2psGetFileFormat(), out.str().c_str()); } void vtkGL2PSUtilities::DrawPathPDF(vtkPath *path, double rasterPos[3], double windowPos[2], unsigned char rgba[4], double scale[2], double rotateAngle, float strokeWidth, const char *) { vtkFloatArray *points = vtkArrayDownCast(path->GetPoints()->GetData()); vtkIntArray *codes = path->GetCodes(); if (points->GetNumberOfTuples() != codes->GetNumberOfTuples()) { return; } std::stringstream out; out.setf(std::ios_base::left | std::ios_base::fixed); const int floatPrec = 2; const int floatWidth = 6; out.precision(floatPrec); out.width(floatWidth); float curveto[3][2]; float curX = 0; float curY = 0; float *pt = points->GetPointer(0); int *code = codes->GetPointer(0); // These are only used in an assertion, ifdef silences warning on non-debug // builds #ifndef NDEBUG float *ptBegin = pt; int *codeBegin = code; #endif int *codeEnd = code + codes->GetNumberOfTuples(); // Push state. PDF doesn't let you reset the CTM, so the hope is that it is // identity when this block starts... out << "q" << endl; // color out << static_cast(rgba[0])/255.f << " " << static_cast(rgba[1])/255.f << " " << static_cast(rgba[2])/255.f << (strokeWidth > 1e-5 ? " RG" : " rg") << endl; // translation out << 1.f << " " << 0.f << " " << 0.f << " " << 1.f << " " << windowPos[0] << " " << windowPos[1] << " cm" << endl; // rotate float sT = sin(vtkMath::RadiansFromDegrees(rotateAngle)); float cT = cos(vtkMath::RadiansFromDegrees(rotateAngle)); out << cT << " " << sT << " " << -sT << " " << cT << " " << 0.f << " " << 0.f << " cm" << endl; // scale if (scale) { out << scale[0] << " " << 0.f << " " << 0.f << " " << scale[1] << " " << 0.f << " " << 0.f << " cm" << endl; } while (code < codeEnd) { assert(pt - ptBegin == (code - codeBegin) * 3); switch (static_cast(*code)) { case vtkPath::MOVE_TO: curX = *pt; curY = *(++pt); ++pt; // Flush z out << curX << " " << curY << " m\n"; break; case vtkPath::LINE_TO: curX = *pt; curY = *(++pt); ++pt; // Flush z out << curX << " " << curY << " l\n"; break; case vtkPath::CONIC_CURVE: // Postscript doesn't support conic curves -- elevate order to cubic: { // Next point should be a CONIC_CURVE as well code += 1; const float &conicX = *pt; const float &conicY = *(++pt); ++pt; // Flush z const float &nextX = *(++pt); const float &nextY = *(++pt); ++pt; // Flush z // Perform degree elevation: curveto[0][0] = (1.0/3.0)*curX + (2.0/3.0)*conicX; curveto[0][1] = (1.0/3.0)*curY + (2.0/3.0)*conicY; curveto[1][0] = (2.0/3.0)*conicX + (1.0/3.0)*nextX; curveto[1][1] = (2.0/3.0)*conicY + (1.0/3.0)*nextY; curveto[2][0] = curX = nextX; curveto[2][1] = curY = nextY; out << curveto[0][0] << " " << curveto[0][1] << endl << curveto[1][0] << " " << curveto[1][1] << endl << curveto[2][0] << " " << curveto[2][1] << " c" << endl; } break; case vtkPath::CUBIC_CURVE: { // Next two points should be CUBIC_CURVEs as well code += 2; curveto[0][0] = *pt; curveto[0][1] = *(++pt); ++pt; curveto[1][0] = *(++pt); curveto[1][1] = *(++pt); ++pt; curveto[2][0] = curX = *(++pt); curveto[2][1] = curY = *(++pt); ++pt; out << curveto[0][0] << " " << curveto[0][1] << endl << curveto[1][0] << " " << curveto[1][1] << endl << curveto[2][0] << " " << curveto[2][1] << " c" << endl; } break; default: out << "% Unrecognized control code: " << *code << endl; pt +=2; break; } ++code; ++pt; } out << "h "; if (strokeWidth > 1e-5) { out << strokeWidth << " w S" << endl; } else { out<< "f" << endl; } out << "Q" << endl; // Pop state glRasterPos3dv(rasterPos); GL2PSrgba colorRgba; colorRgba[0] = rgba[0]/255.0; colorRgba[1] = rgba[1]/255.0; colorRgba[2] = rgba[2]/255.0; colorRgba[3] = rgba[3]/255.0; gl2psSpecialColor(gl2psGetFileFormat(), out.str().c_str(), colorRgba); } void vtkGL2PSUtilities::DrawPathSVG(vtkPath *path, double rasterPos[3], double windowPos[2], unsigned char rgba[4], double scale[2], double rotateAngle, float strokeWidth, const char *label) { vtkFloatArray *points = vtkArrayDownCast(path->GetPoints()->GetData()); vtkIntArray *codes = path->GetCodes(); if (points->GetNumberOfTuples() != codes->GetNumberOfTuples()) { return; } std::stringstream out; out.setf(std::ios_base::left | std::ios_base::fixed); const int floatPrec = 2; const int floatWidth = 6; out.precision(floatPrec); out.width(floatWidth); float curveto[3][2]; float curX = 0; float curY = 0; float *pt = points->GetPointer(0); int *code = codes->GetPointer(0); // Get the size of the render window -- needed to calculate the SVG position. if (!vtkGL2PSUtilities::RenderWindow) { vtkNew dummy; vtkErrorWithObjectMacro(dummy, << "No render window set!"); return; } double windowHeight = static_cast(vtkGL2PSUtilities::RenderWindow->GetSize()[1]); // These are only used in an assertion, ifdef silences warning on non-debug // builds #ifndef NDEBUG float *ptBegin = pt; int *codeBegin = code; #endif if (label != nullptr && label[0] != '\0') { out << "" << endl; } int *codeEnd = code + codes->GetNumberOfTuples(); out << " 1e-5) { out << " fill=\"none\"" << endl << " stroke-width=\"" << strokeWidth << "\"" << endl << " stroke=\"rgb(" << std::setprecision(0) << static_cast(rgba[0]) << "," << static_cast(rgba[1]) << "," << static_cast(rgba[2]) << std::setprecision(floatPrec) << ")\"" << endl; } else { out << " stroke=\"none\"" << endl << " fill=\"rgb(" << std::setprecision(0) << static_cast(rgba[0]) << "," << static_cast(rgba[1]) << "," << static_cast(rgba[2]) << std::setprecision(floatPrec) << ")\"" << endl; } out << " opacity=\"" << static_cast(rgba[3])/255.f << "\"\n" << ">" << endl << " (*code)) { case vtkPath::MOVE_TO: curX = *pt; curY = *(++pt); ++pt; // Flush z out << " M " << curX << " " << curY << endl; break; case vtkPath::LINE_TO: curX = *pt; curY = *(++pt); ++pt; // Flush z out << " L " << curX << " " << curY << endl; break; case vtkPath::CONIC_CURVE: { // Next point should be a CONIC_CURVE as well code += 1; const float &conicX = *pt; const float &conicY = *(++pt); ++pt; // Flush z const float &nextX = *(++pt); const float &nextY = *(++pt); ++pt; // Flush z // Perform degree elevation: curveto[0][0] = conicX; curveto[0][1] = conicY; curveto[1][0] = curX = nextX; curveto[1][1] = curY = nextY; out << " Q " << curveto[0][0] << " " << curveto[0][1] << endl << " " << curveto[1][0] << " " << curveto[1][1] << endl; } break; case vtkPath::CUBIC_CURVE: { // Next two points should be CUBIC_CURVEs as well code += 2; curveto[0][0] = *pt; curveto[0][1] = *(++pt); ++pt; curveto[1][0] = *(++pt); curveto[1][1] = *(++pt); ++pt; curveto[2][0] = curX = *(++pt); curveto[2][1] = curY = *(++pt); ++pt; out << " C " << curveto[0][0] << " " << curveto[0][1] << endl << " " << curveto[1][0] << " " << curveto[1][1] << endl << " " << curveto[2][0] << " " << curveto[2][1] << endl; } break; default: out << "" << endl; pt +=2; break; } ++code; ++pt; } out << " \" />" << endl << "" << endl; glRasterPos3dv(rasterPos); gl2psSpecial(gl2psGetFileFormat(), out.str().c_str()); } inline void vtkGL2PSUtilities::ProjectPoint(double point[4], vtkMatrix4x4 *actorMatrix) { // Build transformation matrix double glMatrix[16]; glGetDoublev(GL_PROJECTION_MATRIX, glMatrix); vtkNew projectionMatrix; projectionMatrix->DeepCopy(glMatrix); projectionMatrix->Transpose(); glGetDoublev(GL_MODELVIEW_MATRIX, glMatrix); vtkNew modelviewMatrix; modelviewMatrix->DeepCopy(glMatrix); modelviewMatrix->Transpose(); vtkNew transformMatrix; vtkMatrix4x4::Multiply4x4(projectionMatrix, modelviewMatrix, transformMatrix); if (actorMatrix) { vtkMatrix4x4::Multiply4x4(transformMatrix, actorMatrix, transformMatrix); } double viewport[4]; glGetDoublev(GL_VIEWPORT, viewport); double depthRange[2]; glGetDoublev(GL_DEPTH_RANGE, depthRange); const double halfWidth = viewport[2] * 0.5; const double halfHeight = viewport[3] * 0.5; const double zFactor1 = (depthRange[1] - depthRange[0]) * 0.5; const double zFactor2 = (depthRange[1] + depthRange[0]) * 0.5; vtkGL2PSUtilities::ProjectPoint(point, transformMatrix, viewport, halfWidth, halfHeight, zFactor1, zFactor2); } inline void vtkGL2PSUtilities::ProjectPoint(double point[4], vtkMatrix4x4 *transformMatrix, double viewportOrigin[], double halfWidth, double halfHeight, double zfact1, double zfact2) { // Convert world to clip coordinates: // = [projection] [modelview] [actor matrix] transformMatrix->MultiplyPoint(point, point); // Clip to NDC const double invW = 1.0 / point[3]; point[0] *= invW; point[1] *= invW; point[2] *= invW; // NDC to device: point[0] = point[0] * halfWidth + viewportOrigin[0] + halfWidth; point[1] = point[1] * halfHeight + viewportOrigin[1] + halfHeight; point[2] = point[2] * zfact1 + zfact2; } inline void vtkGL2PSUtilities::ProjectPoints(vtkPoints *points, vtkMatrix4x4 *actorMatrix) { // Build transformation matrix double glMatrix[16]; glGetDoublev(GL_PROJECTION_MATRIX, glMatrix); vtkNew projectionMatrix; projectionMatrix->DeepCopy(glMatrix); projectionMatrix->Transpose(); glGetDoublev(GL_MODELVIEW_MATRIX, glMatrix); vtkNew modelviewMatrix; modelviewMatrix->DeepCopy(glMatrix); modelviewMatrix->Transpose(); vtkNew transformMatrix; vtkMatrix4x4::Multiply4x4(projectionMatrix, modelviewMatrix, transformMatrix); if (actorMatrix) { vtkMatrix4x4::Multiply4x4(transformMatrix, actorMatrix, transformMatrix); } double viewport[4]; glGetDoublev(GL_VIEWPORT, viewport); double depthRange[2]; glGetDoublev(GL_DEPTH_RANGE, depthRange); const double halfWidth = viewport[2] * 0.5; const double halfHeight = viewport[3] * 0.5; const double zFactor1 = (depthRange[1] - depthRange[0]) * 0.5; const double zFactor2 = (depthRange[1] + depthRange[0]) * 0.5; double point[4]; for (vtkIdType i = 0; i < points->GetNumberOfPoints(); ++i) { points->GetPoint(i, point); point[3] = 1.0; vtkGL2PSUtilities::ProjectPoint(point, transformMatrix, viewport, halfWidth, halfHeight, zFactor1, zFactor2); points->SetPoint(i, point); } } void vtkGL2PSUtilities::UnprojectPoint(double point[4], vtkMatrix4x4 *invTransformMatrix, double viewportOrigin[], double halfWidth, double halfHeight, double zfact1, double zfact2) { point[0] = (point[0] - viewportOrigin[0] - halfWidth) / halfWidth; point[1] = (point[1] - viewportOrigin[1] - halfHeight) / halfHeight; point[2] = (point[2] - zfact2) / zfact1; point[0] *= point[3]; point[1] *= point[3]; point[2] *= point[3]; invTransformMatrix->MultiplyPoint(point, point); } void vtkGL2PSUtilities::UnprojectPoints(double *points3D, vtkIdType numPoints, vtkMatrix4x4 *actorMatrix) { // Build transformation matrix double glMatrix[16]; glGetDoublev(GL_PROJECTION_MATRIX, glMatrix); vtkNew projectionMatrix; projectionMatrix->DeepCopy(glMatrix); projectionMatrix->Transpose(); glGetDoublev(GL_MODELVIEW_MATRIX, glMatrix); vtkNew modelviewMatrix; modelviewMatrix->DeepCopy(glMatrix); modelviewMatrix->Transpose(); vtkNew transformMatrix; vtkMatrix4x4::Multiply4x4(projectionMatrix, modelviewMatrix, transformMatrix); if (actorMatrix) { vtkMatrix4x4::Multiply4x4(transformMatrix, actorMatrix, transformMatrix); } transformMatrix->Invert(); double viewport[4]; glGetDoublev(GL_VIEWPORT, viewport); double depthRange[2]; glGetDoublev(GL_DEPTH_RANGE, depthRange); const double halfWidth = viewport[2] * 0.5; const double halfHeight = viewport[3] * 0.5; const double zFactor1 = (depthRange[1] - depthRange[0]) * 0.5; const double zFactor2 = (depthRange[1] + depthRange[0]) * 0.5; for (vtkIdType i = 0; i < numPoints; ++i) { double *point = points3D + (i * 4); vtkGL2PSUtilities::UnprojectPoint(point, transformMatrix, viewport, halfWidth, halfHeight, zFactor1, zFactor2); } }