5

I am new to Python and have been working with the turtle module as a way of learning the language.

Thanks to stackoverflow, I researched and learned how to copy the image into an encapsulated postscript file and it works great. There is one problem, however. The turtle module allows background color which shows on the screen but does not show in the .eps file. All other colors, i.e. pen color and turtle color, make it through but not the background color.

As a matter of interest, I do not believe the import of Tkinter is necessary since I do not believe I am using any of the Tkinter module here. I included it as a part of trying to diagnose the problem. I had also used bgcolor=Orange rather than the s.bgcolor="orange".

No Joy.

I am including a simple code example:

# Python 2.7.3 on a Mac

import turtle
from Tkinter import *

s=turtle.Screen()
s.bgcolor("orange")

bob = turtle.Turtle()
bob.circle(250)

ts=bob.getscreen()
ts.getcanvas().postscript(file = "turtle.eps")

I tried to post the images of the screen and the .eps file but stackoverflow will not allow me to do so as a new user. Some sort of spam prevention. Simple enough to visualize though, screen has background color of orange and the eps file is white.

output produced from .eps file

I would appreciate any ideas.

martineau
  • 119,623
  • 25
  • 170
  • 301
Returning to Coding
  • 365
  • 1
  • 4
  • 14
  • I get a bus error when it gets to that part ... Hmmm ... – mgilson Nov 24 '12 at 01:33
  • What happens if you do: `ts.getcanvas().postscript(file = 'turtle.eps', colormode = 'color')`? – mgilson Nov 24 '12 at 01:34
  • I tried this, adding the colormode = 'color', and no change. Also, all other colors, ie the pens and the turtle color do come through. It seems to be the background only that is not transferring to the .eps file. – Returning to Coding Nov 24 '12 at 02:19

3 Answers3

3

Postscript was designed for making marks on some medium like paper or film, not raster graphics. As such it doesn't have a background color per se that can be set to given color because that would normally be the color of the paper or unexposed film being used.

In order to simulate this you need to draw a rectangle the size of the canvas and fill it with the color you want as the background. I didn't see anything in the turtle module to query the canvas object returned by getcanvas() and the only alternative I can think of is to read the turtle.cfg file if there is one, or just hardcode the default 300x400 size. You might be able to look at the source and figure out where the dimensions of the current canvas are stored and access them directly.

Update:

I was just playing around in the Python console with the turtle module and discovered that what the canvas getcanvas() returns has a private attribute called _canvas which is a <Tkinter.Canvas instance>. This object has winfo_width() and winfo_height() methods which seem to contain the dimensions of the current turtle graphics window. So I would try drawing a filled rectangle of that size and see if that gives you what you want.

Update 2:

Here's code showing how to do what I suggested. Note: The background must be drawn before any other graphics are because otherwise the solid filled background rectangle created will cover up everything else on the screen.

Also, the added draw_background() function makes an effort to save and later restore the graphics state to what it was. This may not be necessary depending on your exact usage case.

import turtle


def draw_background(a_turtle):
    """ Draw a background rectangle. """
    ts = a_turtle.getscreen()
    canvas = ts.getcanvas()
    height = ts.getcanvas()._canvas.winfo_height()
    width = ts.getcanvas()._canvas.winfo_width()

    turtleheading = a_turtle.heading()
    turtlespeed = a_turtle.speed()
    penposn = a_turtle.position()
    penstate = a_turtle.pen()

    a_turtle.penup()
    a_turtle.speed(0)  # fastest
    a_turtle.goto(-width/2-2, -height/2+3)
    a_turtle.fillcolor(turtle.Screen().bgcolor())
    a_turtle.begin_fill()
    a_turtle.setheading(0)
    a_turtle.forward(width)
    a_turtle.setheading(90)
    a_turtle.forward(height)
    a_turtle.setheading(180)
    a_turtle.forward(width)
    a_turtle.setheading(270)
    a_turtle.forward(height)
    a_turtle.end_fill()

    a_turtle.penup()
    a_turtle.setposition(*penposn)
    a_turtle.pen(penstate)
    a_turtle.setheading(turtleheading)
    a_turtle.speed(turtlespeed)

s = turtle.Screen()
s.bgcolor("orange")

bob = turtle.Turtle()
draw_background(bob)

ts = bob.getscreen()
canvas = ts.getcanvas()

bob.circle(250)

canvas.postscript(file="turtle.eps")

s.exitonclick()  # optional

And here's the actual output produced (rendered onscreen via Photoshop):

output from eps file

martineau
  • 119,623
  • 25
  • 170
  • 301
  • The underlying Tk postscript generation engine doesn't do anything for the background on the grounds that putting a rectangle underneath just while generating PS is pretty trivial (you can delete it again straight afterwards). The size of the rectangle to use depends on exactly what you're generating PS for; it only _defaults_ to what's visible on the screen… – Donal Fellows Nov 24 '12 at 12:08
  • @DonalFellows: The default size and color of such a background rectangle will be whatever your own code decides they will be since one isn't created automatically. I'm only trying to suggest something to use as a default for the size. – martineau Nov 24 '12 at 18:52
  • Thank you @martineau, your point about postscript being designed as a film replacement makes sense. I will add a large rectangle with height and width of the canvass. I do not believe I have to use Tkinter or Tk directly. I believe the actual variables will be ts.window_width(),ts.window_height(). If I get it to work I'll post the new code for posterity. – Returning to Coding Nov 25 '12 at 07:16
  • @New at Python: Correct, sort of -- you should be able to draw the needed background rectangle using standard turtle graphics commands. However to retrieve the height and width information will require something along the lines of `ts.getcanvas()._canvas.winfo_width()`, so in a real sense you _are_ "using" a part of the underlying Tkinter object. Since `_canvas` starts with an underscore it is a private attribute which theorically might change in the future since it's not a documented part of the `turtle` module. – martineau Nov 25 '12 at 09:33
  • Note you can retrieve the current background color by calling the `.bgcolor()` method of a `turtle.Screen` instance with no arguments. This mean that in your example code the return value of `s.bgcolor()` would be `"orange"`, i.e. the value you set it to at the very beginning. – martineau Nov 25 '12 at 09:52
  • In the function `draw_background()`, `bob` should be replaced by `a_turtle`. – Bart Schuijt Feb 24 '20 at 19:34
  • 1
    @Bart: Fixed. Thanks. – martineau Feb 24 '20 at 21:21
1

I haven't found a way to get the canvas background colour on the generated (Encapsulated) PostScript file (I suspect it isn't possible). You can however fill your circle with a colour, and then use Canvas.postscript(colormode='color') as suggested by @mgilson:

import turtle

bob = turtle.Turtle()
bob.fillcolor('orange')
bob.begin_fill()
bob.circle(250)
bob.begin_fill()

ts = bob.getscreen()
ts.getcanvas().postscript(file='turtle.eps', colormode='color')
Pedro Romano
  • 10,973
  • 4
  • 46
  • 50
  • 1
    I appreciate the comment. This would solve the problem if I were only trying to add fill color. The idea would be to get the bgcolor variable to come through. Lets wait and see if anyone has another answer. Thanks for your help. – Returning to Coding Nov 24 '12 at 02:27
0

Improving @martineau's code after a decade


import turtle as t

Screen=t.Screen()

Canvas=Screen.getcanvas()
Width, Height = Canvas.winfo_width(), Canvas.winfo_height()
HalfWidth, HalfHeight = Width//2, Height//2

Background = t.Turtle()
Background.ht()
Background.speed(0)

def BackgroundColour(Colour:str="white"):
    Background.clear() # Prevents accumulation of layers
    Background.penup()
    Background.goto(-HalfWidth,-HalfHeight)
    
    Background.color(Colour)
    Background.begin_fill()
    
    Background.goto(HalfWidth,-HalfHeight)
    Background.goto(HalfWidth,HalfHeight)
    Background.goto(-HalfWidth,HalfHeight)
    Background.goto(-HalfWidth,-HalfHeight)
    
    Background.end_fill()

    Background.penup()
    Background.home()

BackgroundColour("orange")

Bob=t.Turtle()
Bob.circle(250)

Canvas.postscript(file="turtle.eps")

This depends on what a person is trying to accomplish but generally, having the option to select which turtle to use to draw your background to me is unnecessary and can overcomplicate things so what one can do instead is have one specific turtle (which I named Background) to just update the background when desired.

Plus, rather than directing the turtle object via magnitude and direction with setheading() and forward(), its cleaner (and maybe faster) to simply give the direct coordinates of where the turtle should go.

Also for any newcomers: Keeping all of the constants like Canvas, Width, and Height outside the BackgroundColour() function speeds up your code since your computer doesn't have to recalculate or refetch any values every time the function is called.

yungmaz13
  • 139
  • 11