matplotlib: get text bounding box, independent of backend

前端 未结 2 1878
孤街浪徒
孤街浪徒 2020-12-09 20:03

I would like to get the bounding box (dimensions) around some text in a matplotlib figure. The post here, helped me realize that I can use the method text.get_window_

相关标签:
2条回答
  • 2020-12-09 20:53

    Here is my solution/hack. @tcaswell suggested I look at how matplotlib handles saving figures with tight bounding boxes. I found the code for backend_bases.py on Github, where it saves the figure to a temporary file object simply in order to get the renderer from the cache. I turned this trick into a little function that uses the built-in method get_renderer() if it exists in the backend, but uses the save method otherwise.

    def find_renderer(fig):
    
        if hasattr(fig.canvas, "get_renderer"):
            #Some backends, such as TkAgg, have the get_renderer method, which 
            #makes this easy.
            renderer = fig.canvas.get_renderer()
        else:
            #Other backends do not have the get_renderer method, so we have a work 
            #around to find the renderer.  Print the figure to a temporary file 
            #object, and then grab the renderer that was used.
            #(I stole this trick from the matplotlib backend_bases.py 
            #print_figure() method.)
            import io
            fig.canvas.print_pdf(io.BytesIO())
            renderer = fig._cachedRenderer
        return(renderer)
    

    Here are the results using find_renderer() with a slightly modified version of the code in my original example. With the TkAgg backend, which has the get_renderer() method, I get:

    With the MacOSX backend, which does not have the get_renderer() method, I get:

    Obviously, the bounding box using MacOSX backend is not perfect, but it is much better than the red box in my original question.

    0 讨论(0)
  • 2020-12-09 20:54

    If you would like to get the tight bounding box of a rotated text region, here is a possible solution.

    # generate text layer
    def text_on_canvas(text, myf, ro, margin = 1):
        axis_lim = 1
    
        fig = plt.figure(figsize = (5,5), dpi=100)
        plt.axis([0, axis_lim, 0, axis_lim])
    
        # place the left bottom corner at (axis_lim/20,axis_lim/20) to avoid clip during rotation
        aa = plt.text(axis_lim/20.,axis_lim/20., text, ha='left', va = 'top', fontproperties = myf, rotation = ro, wrap=True)
        plt.axis('off')
        text_layer = fig2img(fig) # convert to image
        plt.close()
    
        we = aa.get_window_extent()
        min_x, min_y, max_x, max_y = we.xmin, 500 - we.ymax, we.xmax, 500 - we.ymin
        box = (min_x-margin, min_y-margin, max_x+margin, max_y+margin)
    
        # return coordinates to further calculate the bbox of rotated text
        return text_layer, min_x, min_y, max_x, max_y 
    
    
    def geneText(text, font_family, font_size, style):
        myf = font_manager.FontProperties(fname=font_family, size=font_size)
        ro = 0
    
        if style < 8: # rotated text
            # no rotation, just to get the minimum bbox
            htext_layer, min_x, min_y, max_x, max_y = text_on_canvas(text, myf, 0)
    
            # actual rotated text
            ro = random.randint(0, 90)
            M = cv2.getRotationMatrix2D((min_x,min_y),ro,1)
            # pts is 4x3 matrix
            pts = np.array([[min_x, min_y, 1],[max_x, min_y, 1],[max_x, max_y, 1],[min_x, max_y,1]]) # clockwise
            affine_pts = np.dot(M, pts.T).T
            #print affine_pts
            text_layer, _, _, _, _ = text_on_canvas(text, myf, ro)
    
            visualize_points(htext_layer, pts)
            visualize_points(text_layer, affine_pts)
    
            return text_layer  
    
        else:
            raise NotImplementedError
    
    
    fonts = glob.glob(fonts_path + '/*.ttf')
    ret = geneText('aaaaaa', fonts[0], 80, 1)
    

    The result looks like this: The first one is un-rotated, and the second one is rotated text region. The full code snippet is here.

    0 讨论(0)
提交回复
热议问题