糟糕的数学或糟糕的编程,也许两者都有

时间:2011-04-26 06:03:28

标签: python geometry python-imaging-library

我正在编写一个Python程序来生成着名的Heinlein小说The Moon is a Harsh Mistress中的Luna Free State标志,作为个人项目。我一直在抨击纹章规则并在网上匹配数学公式,但在我的bendsinister例程中有些事情显然是错误的,因为在取消注释时断言失败了。弯曲阴险区域应该是旗帜总面积的1/3,而不是。我做过的唯一真正狡猾的事情是猜测梯形高度的公式,但我猜错误可能在任何地方。我已经删除了大部分代码,只留下了显示问题的必要条件。希望没有数学挑战的人可以发现错误!

#!/usr/bin/python
'generate bend sinister according to rules of heraldry'
import sys, os, random, math, Image, ImageDraw
FLAG = Image.new('RGB', (900, 600), 'black')
CANVAS = ImageDraw.Draw(FLAG)
DEBUGGING = True

def bendsinister(image = FLAG, draw = CANVAS):
 '''a bend sinister covers 1/3 of the field, sinister chief to dexter base

    (some sources on the web say 1/5 of the field, but we'll use 1/3)
    the "field" in this case being the area of the flag, so we need to
    find a trapezoid which is 1/6 the total area (width * height).

    we need to return only the width of the diagonal, which is double
    the height of the calculated trapezoid
 '''
 x, y = image.size
 b = math.sqrt((x ** 2) + (y ** 2))
 A = float(x * y)
 debug('%d * %d = %d' % (x, y, A))
 H = triangle_height(A / 2, b)  # height of triangular half of flag
 width = trapezoid_height(b, H, A / 6) * 2
 if command == 'bendsinister':
  show_bendsinister(x, y, width, image, draw)
 return width

def show_bendsinister(x, y, width, image = FLAG, draw = CANVAS):
 'for debugging formula'
 dexter_base, sinister_chief = (0, y), (x, 0)
 draw.line((dexter_base, sinister_chief), 'blue', int(width))
 image.show()
 debug(image.getcolors(2))  # should be twice as many black pixels as blue

def triangle_height(a, b):
 'a=bh/2'
 h = float(a) / (float(b) / 2)
 debug('triangle height: %.2f' % h)
 return h

def trapezoid_height(b, H, a):
 '''calculate trapezoid height (h) given the area (a) of the trapezoid and
    base b, the longer base, when it is known that the trapezoid is a section
    of a triangle of height H, such that the top, t, equals b when h=0 and
    t=0 when h=H. h is therefore inversely proportional to t with the formula
    t=(1-(h/H))*b, found simply by looking for what fit the two extremes.
    the area of a trapezoid is simply the height times the average length of
    the two bases, b and t, i.e.: a=h*((b+t)/2). the formula reduces
    then to (2*a)/b=(2*h)+(h**2)/H, which is the quadratic equation
    (1/H)*(h**2)+(2*h)-((2*a)/b)=0; solve for h using the quadratic formula
 '''
 try:
  h = (-2 + math.sqrt(4 - 4 * (1.0 / H) * -((2 * a) / b))) / (2 * (1.0 / H))
  debug('trapezoid height with plus: %.2f' % h)
 except:  # must be imaginary, so try minus instead
  h = (-2 - math.sqrt(4 - 4 * (1.0 / H) * -((2 * a) / b))) / (2 * (1.0 / H))
  debug('trapezoid height with minus: %.2f' % h)
 t = (1 - (float(h) / H)) * b
 debug('t=%d, a=%d, check=%d' % (t, round(a), round(h * ((b + t) / 2))))
 #assert round(a) == round(h * ((b + t) / 2))
 return h

def debug(message):
 if DEBUGGING:
  print >>sys.stderr, message

if __name__ == '__main__':
 command = os.path.splitext(os.path.basename(sys.argv[0]))[0]
 print eval(command)(*sys.argv[1:]) or ''

这是调试输出,显示我远离1/3区域:

jcomeau@intrepid:~/rentacoder/jcomeau/tanstaafl$ ./bendsinister.py 
900 * 600 = 540000
triangle height: 499.23
trapezoid height with plus: 77.23
t=914, a=90000, check=77077
[(154427, (0, 0, 255)), (385573, (0, 0, 0))]
154.462354191

这是输出的图像,添加了一些行: bend sinister 红线划分两个三角形,或者可以用于计算梯形。我正在使用从左上角开始的那个。绿线是该三角形的高度,即程序中的变量H.

<小时/> 对于完成的脚本和标志(使用Michael Anderson提供的更正),请参阅http://unternet.net/tanstaafl/。谢谢大家的帮助!

2 个答案:

答案 0 :(得分:8)

将矩形分成两个三角形。它们是完全相同的。

黑色三角形+蓝色梯形是三角形A. 黑三角本身就是三角B

三角形A和三角形B是相似的三角形,因此它们的面积与相关的比例因子的平方相关。

我们希望Blue Trapezoid是Triangle A区域的三分之一。(这样弯曲将占整个矩形的三分之一)。这意味着三角形B必须是三角形A的2/3区域。因此,比例因子必须是sqrt(2/3)。

然后,您应该能够将其转换为非常容易地为您提供弯曲几何的坐标。

答案 1 :(得分:2)

我在IDLE会话中执行了以下代码

from PIL import Image, ImageDraw
from math import sqrt

'generate bend sinister according to rules of heraldry'
import sys, os, random, math
FLAG = Image.new('RGB', (900, 600), 'black')
CANVAS = ImageDraw.Draw(FLAG)
DEBUGGING = True

def debug(message):
    if DEBUGGING:
        print >>sys.stderr, message


def show_bendsinister(x, y, width, image = FLAG, draw = CANVAS):
 'for debugging formula'
 dexter_base, sinister_chief = (0, y), (x, 0)
 print 'dexter_base==',dexter_base,'sinister_chief==',sinister_chief
 draw.line((dexter_base, sinister_chief), 'blue', int(width))
 image.show()
 debug(image.getcolors(2))  # should be twice as many black pixels as blue

def trapezoid_height(x, y, P):
 '''Given a rectangle whose width and length are (x) and (y)

The half of this rectangle is a large triangle A
whose base (b) is the diagonal of the rectangle
and its height (H) goes from its base (b) to
the right angle of the large triangle.
(x) and (y) are the side-lengths of the triangle.
The area of this large triangle is (x*y)/2 = (H*b)/2

Given a trapezoid whose base is the diagonal (b) of the rectangle
and base (b) of the large triangle, its height is (h)
and its top is (t).
Given (S) as the area of the trapezoid.
In general, the trapezoid is disymtric because the triangle have x != y.
So the area is S = h*(b + t)/2

This function trapezoid_height() calculates the height (h) of the trapezoid
in order that the trapezoid have an area (S) which must be
the percentage (P) of the area of the large triangle A. So:
h*(b + t)/2 = S = P*[H*b /2]  ==> h*(b + t) = P*H*b
==> h*t = P*H*b - h*b ==> h*t*(H-h) = [P*H - h]*b*(H-h)

The large triangle is the sum of the trapezoid and of a little triangle B
having an height equal to (H-h) and a base which is the top (t)
of the trapezoid.
The area of this little triangle B is t*(H-h)/2 and must be equal to (1-P)*[H*b / 2]
==> t*(H-h) = (1-P)*H*b ==> h*t*(H-h) = h*(1-P)*H*b

From h*t*(H-h) = [P*H - h]*b*(H-h)  and  h*t*(H-h) = h*(1-P)*H*b
we obtain [P*H - h]*b*(H-h) = h*(1-P)*H*b
==> b*h**2 - (b*H + xy)*h + P*x*y*H = 0
==> h**2 - 2*H*h + P*(H**2) = 0
That leads to the solution H*(1 - sqrt(1-P)), the other H*(1 + sqrt(1-P))
being bigger than H
'''

 H = math.sqrt( (x*x*y*y) / (x*x + y*y) )
 return H*(1 - sqrt(1-P))



def bendsinister(image = FLAG, draw = CANVAS):
 '''a bend sinister covers 1/3 of the field, sinister chief to dexter base

    (some sources on the web say 1/5 of the field, but we'll use 1/3)
    the "field" in this case being the area of the flag, so we need to
    find a trapezoid which is 1/6 the total area (width * height).

    we need to return only the width of the diagonal, which is double
    the height of the calculated trapezoid
 '''
 x, y = image.size
 print 'x ==',x,'y ==',y
 percentage = float(1)/3
 width = 2 * trapezoid_height(x, y , percentage)
 print 'height ==',width/2
 print 'width==',width


 if command == 'bendsinister':
  show_bendsinister(x, y, width, image, draw)
 return width

command = 'bendsinister'
print bendsinister()

结果

x == 900 y == 600
height == 91.6103029364
width== 183.220605873
dexter_base== (0, 600) sinister_chief== (900, 0)
[(180340, (0, 0, 255)), (359660, (0, 0, 0))]
183.220605873

显示的蓝色条纹不会给人的印象是场地面积的1/3,但数字会说:

359660 / 180340 = 1.994344