I would like to draw on top of render image and save it. For example, I would like to draw a text or a line and then save the final image as PNG.

I was thinking to use something like this: Is it possible to create image data and save to a file from a script

But, to make a line here or text would be a nightmare calculating each pixel position. I would like to use OpenGL primitives for drawing on top.

  • $\begingroup$ Are you working on displaying measurements in rendered images? :D Great addon btw. Thanks for making it :) $\endgroup$
    – gandalf3
    Jun 5 '15 at 9:42
  • $\begingroup$ How about making lines and texts as Blender 3D objects? For example a line can be done as a thin cylinder and a text as Blender 3D text. $\endgroup$
    – vvoovv
    Jun 5 '15 at 12:02
  • 1
    $\begingroup$ Yes, I'm working on render MeasureIt, and about adding objects for render, I don't like the idea to fill the scene with garbage objects. Anyway, I think I have one idea that works. $\endgroup$
    – Antonioya
    Jun 5 '15 at 16:08
  • 1
    $\begingroup$ render the measurements to SVG that way they can be styled easily too using groups and classes and ids, and composited later. $\endgroup$
    – zeffii
    Jun 6 '15 at 21:09

Well, I think I have an idea of how to do it:

import bpy
import bgl
import blf
import bpy_extras.image_utils as img_utils

# Get the render image
path = "c:\\tmp\\tmp_render.png"
    result = bpy.data.images['Render Result']
    if result.has_data is False:
        result = bpy.data.images['Render Result']
    result = bpy.data.images['Render Result']

# Save and reload
img = img_utils.load_image(path)

viewport_info = bgl.Buffer(bgl.GL_INT, 4)
bgl.glGetIntegerv(bgl.GL_VIEWPORT, viewport_info)

scene = bpy.context.scene
render_scale = scene.render.resolution_percentage / 100

WIDTH  = int(scene.render.resolution_x * render_scale)
HEIGHT = int(scene.render.resolution_y * render_scale)

# Load image on memory
img.gl_load(0, bgl.GL_NEAREST, bgl.GL_NEAREST)
tex = img.bindcode

# Create output image (to apply texture)
out = bpy.data.images.new("output", WIDTH, HEIGHT)
buffer = bgl.Buffer(bgl.GL_FLOAT, WIDTH * HEIGHT * 4)

bgl.glDisable(bgl.GL_SCISSOR_TEST) # if remove this line, get blender screenshot not image 
bgl.glViewport(0, 0, WIDTH, HEIGHT)

bgl.gluOrtho2D(0, WIDTH, 0, HEIGHT)

# Clear
bgl.glClearColor(0.0, 0.0, 0.0, 0.0)

bgl.glBindTexture(bgl.GL_TEXTURE_2D, tex)

bgl.glColor3f(1.0, 1.0, 1.0)
bgl.glTexCoord2f(0.0, 0.0)
bgl.glVertex2f(0.0, 0.0)
bgl.glTexCoord2f(1.0, 0.0)
bgl.glVertex2f(WIDTH, 0.0)
bgl.glTexCoord2f(1.0, 1.0)
bgl.glVertex2f(WIDTH, HEIGHT)
bgl.glTexCoord2f(0.0, 1.0)
bgl.glVertex2f(0.0, HEIGHT)

bgl.glColor4f(1.0, 0.0, 0.0, 1.0)
#bgl.glTexCoord2f(0.0, 0.0)
bgl.glVertex2f(0.0, 0.0)
#bgl.glTexCoord2f(1.0, 1.0)
bgl.glVertex2f(WIDTH, HEIGHT)

# Draw a Text
font_id = 0
bgl.glColor4f(1.0, 1.0, 0.0, 1.0)
blf.size(font_id, 18, 72)
blf.position(font_id, 0.5, 0.5, 0)
blf.draw(font_id, "Hello World")   

bgl.glReadPixels(0, 0, WIDTH, HEIGHT , bgl.GL_RGBA, bgl.GL_FLOAT, buffer) # read image data
out.pixels = buffer[:] # Assign image data
img.gl_free() # free opengl image memory

# restore opengl defaults
bgl.glColor4f(0.0, 0.0, 0.0, 1.0)

Result:enter image description here

  • $\begingroup$ You are using the OpenGL context which is used by blender to render the UI, viewport, scene etc. This is actually a hack. bgl.glViewport may use a section of the screen but indeed, it can not be larger than the screen. $\endgroup$ Jun 6 '15 at 15:41
  • $\begingroup$ You might render patches (set the view matrix accordingly for each piece) and stitch them together in the image. $\endgroup$ Jun 6 '15 at 15:45
  • $\begingroup$ @pink vertex I'm not an expert on this, could you provide me any example of render by parts and put together? $\endgroup$
    – Antonioya
    Jun 6 '15 at 16:29
  • $\begingroup$ @Antonioya, See what happens if you put some color for the text. $\endgroup$
    – Mano-Wii
    Jun 6 '15 at 16:55
  • $\begingroup$ @Mano-Wii I have tested:bgl.glColor3f(1.0, 0.0, 1.0) before blf.draw and nothing. I'm missing something :-( $\endgroup$
    – Antonioya
    Jun 6 '15 at 18:07

I think you will have to save the render result, reopen it in the image editor to make it accessible, edit its data and save again.

import bpy
import bpy_extras.image_utils as img_utils

path = "your_file_path"
result = bpy.data.images['Render_Result']
img = img_utils.load_image(path)

See this answer for drawing with OpenGL.

You can also attach a drawing handler to bpy.types.SpaceImageEditor to provide a live preview but i don't think you can alter the Render_Result in 2.74 from the python api directly.

Rendering a Tile


bgl.gluOrtho2D(dx*col, dx*(col+1), dy*row, dy*(row+1))

Write back the result(see this answer):

tmp = list(img[:]) 
#copy the image once
#modify tmp for each tile    
#finally copy tmp to output image

#def set_pixel(data, x, y, width, value, channels=4):
#    for i in range(channels):
#        img[channels * (y*width + x) + i] = value[i]

#copy lines
for k in range(TILE_Y):
   i0 = 4*(
       WIDTH  * (row * TILE_Y + k) +
       TILE_X *  col               
   i1 = i0 + TILE_X*4
   tmp[i0:i1] = buffer[k*TILE_X*4: (k+1)*TILE_X*4]      
  • $\begingroup$ Yes, I don't think you can draw directly, my idea was save and redraw on top. Thanks! I think this can help me. $\endgroup$
    – Antonioya
    Jun 5 '15 at 13:22

I liked your solution @Antonioya. You can clear the even more script. For example, this script here already is with draw_callback_px function and with a modal.

import bpy
import bgl
import blf

def draw_callback_px(self, context):

    font_id = 0  # XXX, need to find out how best to get this.

    # draw some text
    bgl.glColor4f(1.0, 1.0, 1.0, 0.8)
    blf.position(font_id, 15, 30, 0)
    blf.size(font_id, 20, 72)
    blf.draw(font_id, "Hello Word " )

    bgl.glColor4f(0.0, 0.0, 0.0, 0.5)

    bgl.glColor3f(1.0, 0.0, 0.0)
    bgl.glVertex2f(100, 250)
    bgl.glVertex2f(400, 300)

    # restore opengl defaults
    bgl.glColor4f(0.0, 0.0, 0.0, 1.0)

class ModalDrawOperator(bpy.types.Operator):
    """Draw a line with the mouse"""
    bl_idname = "view3d.modal_operator"
    bl_label = "Simple Modal View3D Operator"

    def modal(self, context, event):

        if event.type == 'LEFTMOUSE' and event.value == 'PRESS':

            # Image resolution
            u = 800
            v = 600

            bgl.glViewport(0, 0, u, v)

            rx = context.region.width
            ry = context.region.height
            bgl.gluOrtho2D(0, rx, 0, ry)

            bgl.glClearColor(0.0, 0.0, 0.0, 0.0)
            bgl.glClear(bgl.GL_COLOR_BUFFER_BIT | bgl.GL_DEPTH_BUFFER_BIT)

            draw_callback_px(self, context)

            out = bpy.data.images.new("output", u, v)
            buffer = bgl.Buffer(bgl.GL_FLOAT, u * v * 4)
            bgl.glReadPixels(0, 0, u, v , bgl.GL_RGBA, bgl.GL_FLOAT, buffer)
            out.pixels = buffer[:]

        elif event.type in {'ESC'}:
            for a in bpy.data.images:

            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            return {'CANCELLED'}

        return {'PASS_THROUGH'}

    def invoke(self, context, event):
        if context.area.type == 'VIEW_3D':
            # the arguments we pass the the callback
            args = (self, context)
            # Add the region OpenGL drawing callback
            # draw in view space with 'POST_PIXEL', 'POST_VIEW' or 'PRE_VIEW'
            self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_PIXEL')

            self.mouse_path = event.mouse_region_x, event.mouse_region_y

            return {'RUNNING_MODAL'}
            self.report({'WARNING'}, "View3D not found, cannot run operator")
            return {'CANCELLED'}

def register():

def unregister():

if __name__ == "__main__":

What it does ... when you press the left mouse button, the script takes the resolution you set for the image, clean (alpha = 0) OpenGL that is in the background, draws once callback function, creates a new image, reads the OpenGL pixels, draws those pixels in the new image and ends.

enter image description here

  • $\begingroup$ do you know how to draw text? I have tested and don't get anything. $\endgroup$
    – Antonioya
    Jun 6 '15 at 7:37
  • $\begingroup$ @Antonioya, For me it worked. Did you make sure you have imported the blf module? $\endgroup$
    – Mano-Wii
    Jun 6 '15 at 11:12
  • $\begingroup$ Yes, blf is in the source code of my example. The line works, but the text don't.. Try my example. $\endgroup$
    – Antonioya
    Jun 6 '15 at 15:25
  • $\begingroup$ I edited the script to show how to display the 'Hello World' text at the bottom Left. Note: In the function glReadPixels, values for pixels that lie outside the window connected to the current GL context are undefined. $\endgroup$
    – Mano-Wii
    Jun 6 '15 at 15:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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