Mercurial > octave
changeset 29433:8811d8274118
Allow arbitrary text rotation onscreen (bug #33118)
* gl-render.{h,cc} (opengl-renderer::draw_texture_image): New private method
with code extracted from draw_image.
(opengl-renderer::render_text (uint8NDArray ...)): New method to prepare
text rendering and finally call draw_texture_image. Let the existing
render_text(std::string,...) call this one after preparing the pixels array
using text2pixels.
(opengl-renderer::set_ortho_coordinates): New method to setup orthogonal
transform.
(opengl-renderer::restore_previous_coordinates): New method to restore
previous transform.
(opengl-renderer::draw_text): Handle clipping manually for the text as well.
(opengl_texture::create): Add new case for uint8 RGBA image data.
* graphics.{in.h,cc} (text::properties::get_extent_matrix): Add a bool argument
"rotated" that defaults to "false". Re-compute the extent matrix if necessary.
(text::properties::get_extent): Make use of get_extent_matrix.
(text::properties::update_text_extent): Always call text2pixels with 0 degrees
rotation.
* __print_parse_opts__.m: By default, also use the opengl renderer for raster
images that include rotated text.
author | Pantxo Diribarne <pantxo.diribarne@gmail.com> |
---|---|
date | Fri, 12 Mar 2021 20:56:19 +0100 |
parents | 4017cefe8374 |
children | 91f32bf0d497 |
files | libinterp/corefcn/gl-render.cc libinterp/corefcn/gl-render.h libinterp/corefcn/graphics.cc libinterp/corefcn/graphics.in.h scripts/plot/util/private/__print_parse_opts__.m |
diffstat | 5 files changed, 236 insertions(+), 77 deletions(-) [+] |
line wrap: on
line diff
--- a/libinterp/corefcn/gl-render.cc Fri Mar 12 19:12:04 2021 +0100 +++ b/libinterp/corefcn/gl-render.cc Fri Mar 12 20:56:19 2021 +0100 @@ -182,7 +182,7 @@ dim_vector dv (data.dims ()); // Expect RGB data - if (dv.ndims () == 3 && dv(2) == 3) + if (dv.ndims () == 3 && (dv(2) == 3 || dv(2) == 4)) { // FIXME: dim_vectors hold octave_idx_type values. // Should we check for dimensions larger than intmax? @@ -274,7 +274,7 @@ glfcns.glTexImage2D (GL_TEXTURE_2D, 0, 3, tw, th, 0, GL_RGB, GL_UNSIGNED_SHORT, a); } - else if (data.is_uint8_type ()) + else if (data.is_uint8_type () && dv(2) == 3) { const uint8NDArray xdata = data.uint8_array_value (); @@ -293,6 +293,26 @@ glfcns.glTexImage2D (GL_TEXTURE_2D, 0, 3, tw, th, 0, GL_RGB, GL_UNSIGNED_BYTE, a); } + else if (data.is_uint8_type () && dv(2) == 4) + { + const uint8NDArray xdata = data.uint8_array_value (); + + OCTAVE_LOCAL_BUFFER (GLubyte, a, (4*tw*th)); + + for (int i = 0; i < h; i++) + { + for (int j = 0, idx = i*tw*4; j < w; j++, idx += 4) + { + a[idx] = xdata(i,j,0); + a[idx+1] = xdata(i,j,1); + a[idx+2] = xdata(i,j,2); + a[idx+3] = xdata(i,j,3); + } + } + + glfcns.glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, tw, th, 0, + GL_RGBA, GL_UNSIGNED_BYTE, a); + } else { ok = false; @@ -578,8 +598,6 @@ void combine (GLdouble xyz[3], void *data[4], GLfloat w[4], void **out_data) { - //printf ("patch_tessellator::combine\n"); - vertex_data::vertex_data_rep *v[4]; int vmax = 4; @@ -3839,6 +3857,52 @@ } void + opengl_renderer::set_ortho_coordinates (void) + { +#if defined (HAVE_OPENGL) + + m_glfcns.glMatrixMode (GL_PROJECTION); + m_glfcns.glPushMatrix (); + m_glfcns.glLoadIdentity (); + + Matrix vp = get_viewport_scaled (); + m_glfcns.glOrtho (0, vp(2), vp(3), 0, xZ1, xZ2); + m_glfcns.glMatrixMode (GL_MODELVIEW); + m_glfcns.glPushMatrix (); + m_glfcns.glLoadIdentity (); + +#else + + // This shouldn't happen because construction of opengl_renderer + // objects is supposed to be impossible if OpenGL is not available. + + panic_impossible (); + +#endif + } + + void + opengl_renderer::restore_previous_coordinates (void) + { +#if defined (HAVE_OPENGL) + + // Restore previous coordinate system + m_glfcns.glMatrixMode (GL_MODELVIEW); + m_glfcns.glPopMatrix(); + m_glfcns.glMatrixMode (GL_PROJECTION); + m_glfcns.glPopMatrix(); + +#else + + // This shouldn't happen because construction of opengl_renderer + // objects is supposed to be impossible if OpenGL is not available. + + panic_impossible (); + +#endif + } + + void opengl_renderer::draw_text (const text::properties& props) { #if defined (HAVE_OPENGL) @@ -3848,31 +3912,22 @@ Matrix pos = xform.scale (props.get_data_position ()); - // Handle clipping manually when drawing text background + // Handle clipping manually when drawing text in ortho coordinates if (! props.is_clipping () || (clip_code (pos(0), pos(1), pos.numel () > 2 ? pos(2) : 0.0) == 0x40)) { set_clipping (false); + draw_text_background (props); + + set_font (props); + + render_text (props.get_pixels (), props.get_extent_matrix (), + pos(0), pos(1), pos(2), props.get_rotation ()); + set_clipping (props.is_clipping ()); } - set_font (props); - - const Matrix bbox = props.get_extent_matrix (); - - bool blend = m_glfcns.glIsEnabled (GL_BLEND); - - m_glfcns.glEnable (GL_BLEND); - m_glfcns.glEnable (GL_ALPHA_TEST); - m_glfcns.glRasterPos3d (pos(0), pos(1), pos.numel () > 2 ? pos(2) : 0.0); - m_glfcns.glBitmap (0, 0, 0, 0, bbox(0), bbox(1), nullptr); - m_glfcns.glDrawPixels (bbox(2), bbox(3), GL_RGBA, GL_UNSIGNED_BYTE, - props.get_pixels ().data ()); - m_glfcns.glDisable (GL_ALPHA_TEST); - if (! blend) - m_glfcns.glDisable (GL_BLEND); - #else octave_unused_parameter (props); @@ -3887,7 +3942,7 @@ void opengl_renderer::draw_text_background (const text::properties& props, - bool do_rotate) + bool /*do_rotate*/) { #if defined (HAVE_OPENGL) @@ -3902,15 +3957,7 @@ pos(2), true); // Save current transform matrices and set orthogonal window coordinates - m_glfcns.glMatrixMode (GL_PROJECTION); - m_glfcns.glPushMatrix (); - m_glfcns.glLoadIdentity (); - - Matrix vp = get_viewport_scaled (); - m_glfcns.glOrtho (0, vp(2), vp(3), 0, xZ1, xZ2); - m_glfcns.glMatrixMode (GL_MODELVIEW); - m_glfcns.glPushMatrix (); - m_glfcns.glLoadIdentity (); + set_ortho_coordinates (); // Translate coordinates so that the text anchor is (0,0) m_glfcns.glTranslated (pixpos(0), pixpos(1), -pixpos(2)); @@ -3919,9 +3966,7 @@ // Handle others here. double rotation = props.get_rotation (); - if (do_rotate && rotation != 0.0 && rotation != 90.0 - && rotation != 180.0 && rotation != 270.0) - m_glfcns.glRotated (-rotation, 0.0, 0.0, 1.0); + m_glfcns.glRotated (-rotation, 0.0, 0.0, 1.0); double m = points_to_pixels (props.get_margin ()); const Matrix bbox = props.get_extent_matrix (); @@ -3967,10 +4012,7 @@ set_linestyle ("-"); } - // Restore previous coordinate system - m_glfcns.glPopMatrix(); - m_glfcns.glMatrixMode (GL_PROJECTION); - m_glfcns.glPopMatrix(); + restore_previous_coordinates (); #else @@ -3991,12 +4033,34 @@ #if defined (HAVE_OPENGL) octave_value cdata = props.get_color_data (); + Matrix x = props.get_xdata ().matrix_value (); + Matrix y = props.get_ydata ().matrix_value (); + + draw_texture_image (cdata, x, y); + +#else + + octave_unused_parameter (props); + + // This shouldn't happen because construction of opengl_renderer + // objects is supposed to be impossible if OpenGL is not available. + + panic_impossible (); + +#endif + } + + void + opengl_renderer::draw_texture_image (const octave_value cdata, Matrix x, + Matrix y, bool ortho) + { +#if defined (HAVE_OPENGL) + dim_vector dv (cdata.dims ()); int h = dv(0); int w = dv(1); double x0, x1, y0, y1; - Matrix x = props.get_xdata ().matrix_value (); double dx = 1.0; if (w > 1) dx = (x(1) - x(0)) / (w - 1); @@ -4004,7 +4068,6 @@ x0 = x(0)-dx/2; x1 = x(1)+dx/2; - Matrix y = props.get_ydata ().matrix_value (); double dy = 1.0; if (h > 1) dy = (y(1) - y(0)) / (h - 1); @@ -4013,7 +4076,7 @@ y1 = y(1)+dy/2; // Expect RGB data - if (dv.ndims () == 3 && dv(2) == 3) + if (dv.ndims () == 3 && (dv(2) == 3 || dv(2) == 4)) { opengl_texture tex = opengl_texture::create (m_glfcns, cdata); if (tex.is_valid ()) @@ -4025,16 +4088,28 @@ m_glfcns.glBegin (GL_QUADS); tex.tex_coord (0.0, 0.0); - m_glfcns.glVertex3d (x0, y0, 0.0); + if (ortho) + m_glfcns.glVertex2d (x0, y0); + else + m_glfcns.glVertex3d (x0, y0, 0.0); tex.tex_coord (1.0, 0.0); - m_glfcns.glVertex3d (x1, y0, 0.0); + if (ortho) + m_glfcns.glVertex2d (x1, y0); + else + m_glfcns.glVertex3d (x1, y0, 0.0); tex.tex_coord (1.0, 1.0); - m_glfcns.glVertex3d (x1, y1, 0.0); + if (ortho) + m_glfcns.glVertex2d (x1, y1); + else + m_glfcns.glVertex3d (x1, y1, 0.0); tex.tex_coord (0.0, 1.0); - m_glfcns.glVertex3d (x0, y1, 0.0); + if (ortho) + m_glfcns.glVertex2d (x0, y1); + else + m_glfcns.glVertex3d (x0, y1, 0.0); m_glfcns.glEnd (); m_glfcns.glDisable (GL_TEXTURE_2D); @@ -4045,7 +4120,10 @@ #else - octave_unused_parameter (props); + octave_unused_parameter (cdata); + octave_unused_parameter (x); + octave_unused_parameter (y); + octave_unused_parameter (ortho); // This shouldn't happen because construction of opengl_renderer // objects is supposed to be impossible if OpenGL is not available. @@ -4795,18 +4873,7 @@ uint8NDArray pixels; text_to_pixels (txt, pixels, bbox, halign, valign, rotation); - bool blend = m_glfcns.glIsEnabled (GL_BLEND); - - m_glfcns.glEnable (GL_BLEND); - m_glfcns.glEnable (GL_ALPHA_TEST); - m_glfcns.glRasterPos3d (x, y, z); - m_glfcns.glBitmap(0, 0, 0, 0, bbox(0), bbox(1), nullptr); - m_glfcns.glDrawPixels (bbox(2), bbox(3), - GL_RGBA, GL_UNSIGNED_BYTE, pixels.data ()); - m_glfcns.glDisable (GL_ALPHA_TEST); - - if (! blend) - m_glfcns.glDisable (GL_BLEND); + render_text (pixels, bbox, x, y, z, rotation); } return bbox; @@ -4828,4 +4895,60 @@ #endif } + + void + opengl_renderer::render_text (uint8NDArray pixels, Matrix bbox, + double x, double y, double z, double rotation) + { +#if defined (HAVE_OPENGL) + + // Transform data coordinates to screen pixel ortho coordinates + ColumnVector pixpos = get_transform ().transform (x, y, z, false); + Matrix xdata(1, 2, bbox(0) / m_devpixratio); + xdata(1) += (bbox(2) - 1) / m_devpixratio; + Matrix ydata(1, 2, -bbox(1) / m_devpixratio); + ydata(1) -= (bbox(3) - 1) / m_devpixratio; + + bool blend = m_glfcns.glIsEnabled (GL_BLEND); + m_glfcns.glEnable (GL_BLEND); + m_glfcns.glEnable (GL_ALPHA_TEST); + + set_ortho_coordinates (); + + // Translate coordinates so that the text anchor is (0,0) + m_glfcns.glTranslated (pixpos(0), pixpos(1), -pixpos(2)); + + m_glfcns.glRotated (-rotation, 0.0, 0.0, 1.0); + + // Permute pixels returned by freetype + Array<octave_idx_type> perm (dim_vector (3, 1)); + perm(0) = 2; + perm(1) = 1; + perm(2) = 0; + draw_texture_image (pixels.permute (perm), + xdata, ydata, true); + + restore_previous_coordinates (); + + m_glfcns.glDisable (GL_ALPHA_TEST); + + if (! blend) + m_glfcns.glDisable (GL_BLEND); + +#else + + octave_unused_parameter (pixels); + octave_unused_parameter (bbox); + octave_unused_parameter (x); + octave_unused_parameter (y); + octave_unused_parameter (z); + octave_unused_parameter (rotation); + + // This shouldn't happen because construction of opengl_renderer + // objects is supposed to be impossible if OpenGL is not available. + + panic_impossible (); + +#endif + } }
--- a/libinterp/corefcn/gl-render.h Fri Mar 12 19:12:04 2021 +0100 +++ b/libinterp/corefcn/gl-render.h Fri Mar 12 20:56:19 2021 +0100 @@ -183,8 +183,15 @@ | (z > zmax ? 1 : 0) << 5 | (is_nan_or_inf (x, y, z) ? 0 : 1) << 6); } + + void render_text (uint8NDArray pixels, Matrix bbox, + double x, double y, double z, double rotation); + + void set_normal (int bfl_mode, const NDArray& n, int j, int i); - void set_normal (int bfl_mode, const NDArray& n, int j, int i); + void set_ortho_coordinates (void); + + void restore_previous_coordinates (void); double points_to_pixels (const double val) const; @@ -204,6 +211,9 @@ void draw_all_lights (const base_properties& props, std::list<graphics_object>& obj_list); + void draw_texture_image (const octave_value cdata, + Matrix x, Matrix y, bool ortho = false); + protected: opengl_functions& m_glfcns;
--- a/libinterp/corefcn/graphics.cc Fri Mar 12 19:12:04 2021 +0100 +++ b/libinterp/corefcn/graphics.cc Fri Mar 12 20:56:19 2021 +0100 @@ -7128,7 +7128,7 @@ } else { - Matrix text_ext = text_props.get_extent_matrix (); + Matrix text_ext = text_props.get_extent_matrix (true); // The text extent is returned in device pixels. Unscale and // work with logical pixels @@ -9404,10 +9404,47 @@ } Matrix -text::properties::get_extent_matrix (void) const +text::properties::get_extent_matrix (bool rotated) const { // FIXME: Should this function also add the (x,y) base position? - return extent.get ().matrix_value (); + Matrix ext = extent.get ().matrix_value (); + + if (rotated && get_rotation () != 0) + { + double rot = get_rotation () * 4.0 * atan (1.0) / 180; + double x0 = ext(0) * cos (rot) - ext(1) * sin (rot); + double x1 = x0; + double y0 = ext(0) * sin (rot) + ext(1) * cos (rot); + double y1 = y0; + + double tmp = (ext(0)+ext(2)) * cos (rot) - ext(1) * sin (rot); + x0 = std::min (x0, tmp); + x1 = std::max (x1, tmp); + tmp = (ext(0)+ext(2)) * sin (rot) + ext(1) * cos (rot); + y0 = std::min (y0, tmp); + y1 = std::max (y1, tmp); + + tmp = (ext(0)+ext(2)) * cos (rot) - (ext(1)+ext(3)) * sin (rot); + x0 = std::min (x0, tmp); + x1 = std::max (x1, tmp); + tmp = (ext(0)+ext(2)) * sin (rot) + (ext(1)+ext(3)) * cos (rot); + y0 = std::min (y0, tmp); + y1 = std::max (y1, tmp); + + tmp = ext(0) * cos (rot) - (ext(1)+ext(3)) * sin (rot); + x0 = std::min (x0, tmp); + x1 = std::max (x1, tmp); + tmp = ext(0) * sin (rot) + (ext(1)+ext(3)) * cos (rot); + y0 = std::min (y0, tmp); + y1 = std::max (y1, tmp); + + ext(0) = x0; + ext(1) = y0; + ext(2) = x1 - x0; + ext(3) = y1 - y0; + } + + return ext; } octave_value @@ -9415,7 +9452,7 @@ { // FIXME: This doesn't work right for 3D plots. // (It doesn't in Matlab either, at least not in version 6.5.) - Matrix m = extent.get ().matrix_value (); + Matrix m = get_extent_matrix (true); Matrix pos = get_position ().matrix_value (); Matrix p = convert_text_position (pos, *this, get_units (), "pixels"); @@ -9525,7 +9562,7 @@ octave::autolock guard (gh_mgr.graphics_lock ()); txt_renderer.text_to_pixels (sv.join ("\n"), pixels, bbox, - halign, valign, get_rotation (), + halign, valign, 0.0, get_interpreter ()); // The bbox is relative to the text's position. We'll leave it that // way, because get_position does not return valid results when the
--- a/libinterp/corefcn/graphics.in.h Fri Mar 12 19:12:04 2021 +0100 +++ b/libinterp/corefcn/graphics.in.h Fri Mar 12 20:56:19 2021 +0100 @@ -4528,7 +4528,7 @@ END_PROPERTIES OCTINTERP_API Matrix get_data_position (void) const; - OCTINTERP_API Matrix get_extent_matrix (void) const; + OCTINTERP_API Matrix get_extent_matrix (bool rotated = false) const; const uint8NDArray& get_pixels (void) const { return pixels; } // Text renderer, used for calculation of text size
--- a/scripts/plot/util/private/__print_parse_opts__.m Fri Mar 12 19:12:04 2021 +0100 +++ b/scripts/plot/util/private/__print_parse_opts__.m Fri Mar 12 20:56:19 2021 +0100 @@ -248,18 +248,7 @@ if (strcmp (arg_st.renderer, "auto")) if (opengl_ok && strcmp (graphics_toolkit (arg_st.figure), "qt")) - ## "opengl" renderer only does text rotations of 0°, 90°, 180°, 270°, ... - ht = findall (arg_st.figure, "type", "text"); - if (isempty (ht)) - angles = []; - else - angles = [get(ht, "rotation"){:}]; - endif - if (any (mod (angles, 90))) - arg_st.renderer = "painters"; - else - arg_st.renderer = "opengl"; - endif + arg_st.renderer = "opengl"; else arg_st.renderer = "painters"; endif