If you're writing some Tkinter software and you're sizing something in inches or centimeters and tkinter only gives you feedback in pixel distances then you may need a way to get back to your prefered units of measure. I recently found myself in this situation. There's nothing fancy about the set of functions here, it's more about the little known winfo_fpixels() function. Once you know about this everything else is a piece of cake. These functions are simple, but convenient.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
class tkMath( object ): PIXELS_PER_INCH = 0 PIXELS_PER_CM = 0 PIXELS_PER_MM = 0 PIXELS_PER_POINT = 0 @staticmethod def setup( root ): '''Must be called before any of the methods are used to initialize the conversion constants.''' tkMath.PIXELS_PER_INCH = root.winfo_fpixels( '1i' ) tkMath.PIXELS_PER_CM = root.winfo_fpixels( '1c' ) tkMath.PIXELS_PER_MM = root.winfo_fpixels( '1m' ) tkMath.PIXELS_PER_POINT = root.winfo_fpixels( '1p' ) @staticmethod def pixelsToInches( pixels ): '''Convert pixels (python float or int) to inches.''' return pixels / tkMath.PIXELS_PER_INCH @staticmethod def pixelsToCM( pixels ): '''Convert pixels (python float or int) to centimeters.''' return pixels / tkMath.PIXELS_PER_CM @staticmethod def pixelsToMM( pixels ): '''Convert pixels (python float or int) to millimeters.''' return pixels / tkMath.PIXELS_PER_MM @staticmethod def pixelsToPoints( pixels ): '''Convert pixels (python float or int) to points.''' return pixels / tkMath.PIXELS_PER_POINT @staticmethod def inchesToPixels( inches ): '''Convert inches (python float or int) to pixels.''' return inches * tkMath.PIXELS_PER_INCH @staticmethod def cmToPixels( cm ): '''Convert centimeters (python float or int) to pixels.''' return cm * tkMath.PIXELS_PER_CM def mmToPixels( mm ): '''Convert millimeters (python float or int) to pixels.''' return mm * tkMath.PIXELS_PER_MM def pointsToPixels( points ): '''Convert points (python float or int) to pixels.''' return points * tkMath.PIXELS_PER_POINTS @staticmethod def toPixels( tkCoord ): '''Convenience function for inches, cm, mm and pointsToPixels(). Convert a tkCoord (string appended by 'i', 'c', 'm' or 'p') to pixels.''' if isinstance( tkCoord, str ): if tkCoord[-1] == 'i': return tkMath.inchesToPixels( float(tkCoord[:-1]) ) elif tkCoord[-1] == 'c': return tkMath.cmToPixels( float(tkCoord[:-1]) ) elif tkCoord[-1] == 'm': return tkMath.mmToPixels( float(tkCoord[:-1]) ) elif tkCoord[-1] == 'p': return tkMath.pointsToPixels( float(tkCoord[:-1]) ) else: return float(tkCoord) else: return tkCoord @staticmethod def compare( coord1, coord2 ): '''Compare two tk measures -- they need not be in the same units.''' return tkMath.coordToPixels(coord1) - tkMath.coordToPixels(coord2) @staticmethod def add( coord1, coord2 ): '''Add two tk measures -- they need not be in the same units.''' return tkMath.coordToPixels(coord1) + tkMath.coordToPixels(coord2) @staticmethod def sub( coord1, coord2 ): '''Subtract two tk measures -- they need not be in the same units.''' return tkMath.coordToPixels(coord1) - tkMath.coordToPixels(coord2) @staticmethod def tkPolar( x1, y1, x2, y2 ): '''Calculate the direction (in radians, 3 o'clock is 0, down is 1/2 PI, etc.) and distance (in pixels) between to points. All arguments should be in the same units (python float or int). The result is in the same units as the arguments.''' import math deltaX = math.fabs( x1 - x2 ) deltaY = math.fabs( y1 - y2 ) direction = math.atan2( deltaY, deltaX ) distance = math.sqrt( math.pow(deltaX, 2) + math.pow(deltaY, 2) ) return direction, distance @staticmethod def tkCartesian( x, y, direction, distance ): '''Complementary to tkPolar(). Given a x,y point, direction in radians (0 is at 3 o'clock, 1/2 PI is straight down, etc.) and a distance (all as python float or int). This function returns the x and y of the end-point.''' import math deltaX = distance * math.cos( direction ) deltaY = distance * math.sin( direction ) return x + deltaX, y + deltaY
Clearly I didn't need to define a class since all the methods are static. It's just a design preference. Before any method in this class is called, it's necessary to call setup() and pass it a widget. Any ol' widget will do. This only needs to be done one time. setup() will use the widget to determine the conversion constants. I prefer to call setup immediately after I create my root widget so I called my parameter 'root'.
Each of the six conversion functions requires its argument as a python float or integer value. Python floats are always returned.
tkDistance is a simple Pythagorean Theorem implementation. All four arguments must be in python float or integer values, all in the same units (pixels, inches, cm or mm). The result is a python float in the same units as was entered. I've found common idiom to their usage to be:
root = Tk() tkMath.setup( root )
... def myEventCB( event ): distance = tkMatho.tkDistance( initialx, initialy, event.x, event.y )
distanceInInches = tkMath.pixelsToInches( distance )
vector requires all arguments as python floats and/or ints. direction should be provided in radians with zero at 3 o'clock and values incrementing clock-wise: So straight down is 1/2 * PI, to the left if PI and straight up is 3/2 * PI. With x1,y1 being the starting coordinate for the vector, the result is a tuple x,y of the ending coordinates.
I've found tk measurements to be highly accurate on any display I've experimented with. That is if I do something such as draw a 3 inch line on a canvas widget and measure it with a ruler up against the screen, I get just about three inches.
In the function setup, in these 3 lines
should tkUnits not be replaced with tkMath?
Actually, should all tkUnits not be replaced with tkMath?
Here is another function for the distance between 2 points, especially useful for comparison.
In the last line, x2 should be y1.
In tkPolar, math.atan should be math.atan2.