##############################################################################
# ThanCad 0.0.9 "DoesSomething": 2dimensional CAD with raster support for engineers.
#
# Copyright (c) 2001-2009 Thanasis Stamos, August 23, 2009
# URL: http://thancad.sourceforge.net
# e-mail: cyberthanasis@excite.com
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details (www.gnu.org/licenses/gpl.html).
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
"""\
ThanCad 0.0.9 "DoesSomething": 2dimensional CAD with raster support for engineers.
This module defines the raster image element, based on Python Image Library.
"""
from math import fabs,pi,hypot,cos,sin,atan2
import Image, ImageTk
from itertools import izip
from p_gmath import PI2,thanNearx
from p_ggen import iterby2
import p_gtkuti
import thanintall
from thanvar import Canc,thanfiles
from thandefs import ThanImageMissing
from thantrans import T
from thanelem import ThanElement
from thanline import thanPntNearest,thanPntNearest2,thanPerpPoints,thanSegNearest
############################################################################
############################################################################
class ThanImage(ThanElement):
"""A raster image.
A. Theory
1.We assume that a pixel is rectangular in shape and the the world
coordinates of a pixel correspond to its center.
2.The x world
coordinate corresponds to the column j and the y world coordinate
corresponds to row i. To get the colour of the pixel of a PIL image
we call the function:
image.get((j,i))
where j is the column and i is the row.
3.The row i increases downwards, while the world coordinate y
increases upwards.
4.The column j takes values from 0 to dxp-1, where dxp is the image
width in pixels
The row i takes values from 0 to dxp-1, where dyp is the image
height in pixels.
5.The world image width is dx=self.c2[0]-self.c1[0] and the
the world image height is dy=self.c2[1]-self.c1[1]
6.The world coordinates of the lower left corner of the image
self.c1 (that means x1,y1; z1,t1,.. are ignored here) are the
coordinates of the center of the pixel j=0, i=dyp-1
Likewise the coordinates of the upper right corner of the image
self.c2 are the coordinates of the center of the pixel
j=dxp-1, i=0
7.There are ((dxp-1)-0)+1=dxp pixels in the width and
((dyp-1)-0)+1=dyp pixels in the hight of the image.
However the distance in pixels from the center of its lower left
corner to its upper right corner are dxp-1 and dyp-1 in the
x (column) and y (row) directions respectively.
Suppose 10 pixel in the x (column) direction:
0123456789
OOOOOOOOOO
^ ^
from the center of the zeroth pixel to the center of the nineth
pixel stands the width of 9 pixels.
8.Thus the width dxp1 and the height dyp1 of a pixel in world
coordinates are:
dxp1 = (x2-x1)/(dxp-1)
dyp1 = (y2-y1)/(dyp-1)
9.Thus, because the size of a pixel is small, but not infinitesimal,
the image spans a little more space than intended. Its lower left
and upper right corners are, in reality:
x1-dxp1*0.5, y1-dyp1*0.5
x2+dxp1*0.5, y2+dyp1*0.5
10.In the x (column direction) the correspondence between pixels
and world coordinates are:
x1 -> 0
x1-0.5*dxp1 -> 0
x1+0.5*dxp1 -> 0
x2 -> dxp-1
x2-0.5*dxp1 -> dxp-1
x2+0.5*dxp1 -> dxp-1
In one formula, if we seek the pixel column of world coordinate x:
j = int[(x-x1)/dxp1+0.5] =>
j = int[ (x-x1)/((x2-x1)/(dxp-1)) + 0.5] =>
j = int[ (x-x1)/(x2-x1)*(dxp-1) + 0.5] =>
j = int[ (x-x1)/dx*(dxp-1) + 0.5]
where dx = x2-x1
j = int[ (x-x1)/dx*(dxp-1) + 0.5]
i = int[ (y2-y)/dy*(dyp-1) + 0.5]
11.In the y (row direction) the correspondence between pixels
and world coordinates are:
y2 -> 0
y2-0.5*dyp1 -> 0
y2+0.5*dyp1 -> 0
y1 -> dyp-1
y1-0.5*dyp1 -> dyp-1
y1+0.5*dyp1 -> dyp-1
In one formula, if we seek the pixel column of world coordinate x:
float i = (y2-y)/dyp1
this gives for y=y2:
float i = (y2-y2)/dyp1 = 0 ->correct
this gives for x=x2:
float i = (y2-y1)/dyp1 = (y2-y1)/((y2-y1)/(dyp-1)) = dyp-1 ->correct
The integer value of the pixel is:
i = int[(y2-y)/dyp1 + 0.5] => ... =>
i = int[ (y2-y)/dy*(dyp-1) + 0.5]
where dy = y2-y1
If we put the limits y1-0.5*dyp1, y1+0.5*dyp1, y2-0.5*dyp1,
y2+0.5*dyp1 the results are ok.
12.For additional safety we can limit the world coordinates to
x1 <= x <=x2 and y1 <= y <=y2
even thougth the image in reality is a little longer.
13.The reverse function x, that is if we seek the world x coordinate
of the center of pixel with column=j, row=i:
x = x1+j*dxp1 which for j=0 and j=dxp-1 gives:
x = x1+0*dxp1 = x1 -> correct
x = x1+(dxp-1)*dxp1 = x1+(dxp-1)*(x2-x1)/(dxp-1) =
= x1+(x2-x1) = x2 -> correct
The formula becomes:
x = x1+j*dxp1 = x1 + j*(x2-x1)/(dxp-1) =>
x = x1 + j*dx/(dxp-1)
It must be realised that this x coordinate is just the center of
pixel. The pixel j,i in reality spans:
x1+j*dx/(dxp-1) - dyp1*0.5 <= x <=x1+j*dx/(dxp-1) + dyp1*0.5
14.The reverse function y, that is if we seek the world y coordinate
of the center of pixel with column=j, row=i:
y = y2-i*dyp1 which for i=0 and i=dyp-1 gives:
y = y2-0*dyp1 = y2 -> correct
y = y2-(dyp-1)*dyp1 = y2-(dyp-1)*(y2-y1)/(dyp-1) =
= y2-(y2-y1) = y1 -> correct
The formula becomes:
y = y2-i*dyp1 = y2 - i*(y2-y1)/(dyp-1) =>
y = y2 - i*dy/(dyp-1)
It must be realised that this x coordinate is just the center of
pixel. The pixel j,i in reality spans:
y2-i*dy/(dyp-1) - dyp1*0.5 <= y <=y2-i*dy/(dyp-1) + dyp1*0.5
B. Implementation
1.Because the values dxp-1 and dyp-1 are used more than the dxp,dyp
we set:
dxp, dyp = self.image.size
self.size = dxp-1, dyp-1
So the width and height of the image are one less than the actual
ones.
2.Since dxp-1, dyp-1 are in the denominator the size of the image
must be at least 2 pixels in x and y direction.
"""
thanTkCompound = 100 # The number of Tkinter objects that make the element:
# 2=compound (Image and frame), 100=compound (frame and text)
#===========================================================================
def thanSet (self, filnam, image, c1, c2, theta):
"Sets the attributes of the image."
assert c2[0]>=c1[0] and c2[1]>=c1[1], "Image can not have negative dimensions!"
self.setBoundBox([c1[0], c1[1], c2[0], c2[1]])
self.c1 = c1
self.c2 = c2
dxp, dyp = image.size
if dxp < 2 or dyp < 2:
self.image = ThanImageMissing() # Defaults to size 100x100
dxp, dyp = image.size
self.size = dxp-1, dyp-1 # Make the following computations easier
self.theta = theta % PI2 # radians assumed
self.filnam = filnam
self.image = image
self.imagez = None
self.view = [1e100, 1e100, -1e100, -1e100]
self.visited = None # Placeholder of a tracker object used in semi-automatic trace
# self.thanTags = () # thanTags is initialised in ThanElement
def thanIsNormal(self):
"Returns False if the image is degenerate."
if thanNearx(self.c1[0], self.c2[0]): return False # There is no degenerate image
if thanNearx(self.c1[1], self.c2[1]): return False # There is no degenerate image
return True
def __getstate__(self):
odict = self.__dict__.copy()
del odict["image"] # Do not save the image in the file
del odict["imagez"]
return odict
def __setstate__(self, odict):
self.__dict__.update(odict)
try:
self.image = Image.open(self.filnam)
dxp, dyp = self.image.size # If it is not the same as the saved image,
self.size = dxp-1, dyp-1 # the new image will be fitted in the same area
except IOError:
dxp, dyp = self.size
self.image = ThanImageMissing((dxp+1, dyp+1))
self.imagez = None
def thanClone(self):
"""Makes a geometric clone of itself.
Since PIL.copy() is a lazy operation, the cloned raster DOES NOT
occupy more memory. But, to be sure, we return a reference to the raster
so it is sure that no memory is wasted.
"""
el = ThanImage()
# el.thanSet(self.filnam, self.image.copy(), self.c1, self.c2, self.theta)
el.thanSet(self.filnam, self.image, self.c1, self.c2, self.theta)
return el
def thanRotate(self):
"Rotates the element within XY-plane with predefined angle and rotation angle."
c1 = self.thanRotateXy(self.c1)
self.c2[0] += c1[0]-self.c1[0]
self.c2[1] += c1[1]-self.c1[1]
self.c1 = c1
self.theta += self.rotPhi
self.theta %= PI2
self.setBoundBox([self.c1[0], self.c1[1], self.c2[0], self.c2[1]])
def thanMirror(self):
"Mirrors the element within XY-plane with predefined point and unit vector."
dbig = self.c2[0] - self.c1[0]
cbig = list(self.c1)
cbig[0] += dbig*cos(self.theta)
cbig[1] += dbig*sin(self.theta)
cbig = self.thanMirrorXy(cbig)
c1 = self.thanMirrorXy(self.c1)
self.c2[0] += c1[0]-self.c1[0]
self.c2[1] += c1[1]-self.c1[1]
self.c1 = c1
self.theta = atan2(cbig[1]-self.c1[1], cbig[0]-self.c1[0]) % PI2
self.setBoundBox([self.c1[0], self.c1[1], self.c2[0], self.c2[1]])
def thanScale(self, cs, scale):
"Scales the element in n-space with defined scale and center of scale."
self.c1 = [cs1+(cc1-cs1)*scale for (cc1,cs1) in izip(self.c1, cs)]
self.c2 = [cs1+(cc1-cs1)*scale for (cc1,cs1) in izip(self.c2, cs)]
self.setBoundBox([self.c1[0], self.c1[1], self.c2[0], self.c2[1]])
def thanMove(self, dc):
"Moves the element with defined n-dimensional distance."
self.c1 = [cc1+dd1 for (cc1,dd1) in izip(self.c1, dc)]
self.c2 = [cc1+dd1 for (cc1,dd1) in izip(self.c2, dc)]
self.setBoundBox([self.c1[0], self.c1[1], self.c2[0], self.c2[1]])
def thanOsnap(self, proj, otypes, ccu, eother, cori):
"Return a point of type otype nearest to ccu."
if "ena" not in otypes: return None # Object snap is disabled
cp = self.__boundaryLine()
ps = []
if "end" in otypes:
for c in cp:
ps.append((fabs(c[0]-ccu[0])+fabs(c[1]-ccu[1]), "end", c))
if "mid" in otypes:
for ca, cb in iterby2(cp):
c = [(ca1+cb1)*0.5 for (ca1,cb1) in izip(ca, cb)]
ps.append((fabs(c[0]-ccu[0])+fabs(c[1]-ccu[1]), "mid", c))
if "nea" in otypes:
c = thanPntNearest(cp, ccu)
if c != None:
ps.append((fabs(c[0]-ccu[0])+fabs(c[1]-ccu[1]), "nea", c))
if cori != None and "per" in otypes:
for c in thanPerpPoints(cp, cori):
ps.append((fabs(c[0]-ccu[0])+fabs(c[1]-ccu[1]), "per", c))
if eother != None and "int" in otypes:
ps.extend(thanintall.thanIntsnap(self, eother, ccu, proj))
if len(ps) < 1: return None
return min(ps)
def __boundaryLine(self):
"Returns the boundary as a line (a list of points)."
cp = [self.c1, list(self.c1), self.c2, list(self.c2), self.c1]
cp[1][0] = self.c2[0]
cp[3][0] = self.c1[0]
# cp = [(self.x1, self.y1), (self.x2, self.y1), (self.x2, self.y2), (self.x1, self.y2)]
return cp
def thanPntNearest(self, ccu):
"Finds the nearest point of this line to a point."
return thanPntNearest2(self.__boundaryLine(), ccu)[0]
def thanPntNearest2(self, ccu):
"Finds the nearest point of this line to a point."
return thanPntNearest2(self.__boundaryLine(), ccu)
def thanPerpPoints(self, ccu):
"Finds perpendicular point from ccu to polyline."
return thanPerpPoints(self.__boundaryLine(), ccu)
def thanSegNearest(self, ccu):
"Finds the nearest segment of the surrounding quadrilateral of this image to a point."
return thanSegNearest(self.__boundaryLine(), ccu)
def thanLength(self):
"Returns the length of the polyline."
alx = self.c2[0]-self.c1[0] # Note that alx > 0
aly = self.c2[1]-self.c1[1] # Note that aly > 0
if alx > aly: return alx
return aly
def thanArea(self):
"Returns the area of the polyline."
alx = self.c2[0]-self.c1[0] # Note that alx > 0
aly = self.c2[1]-self.c1[1] # Note that aly > 0
return alx*aly
def thanBreak(self, c1=None, c2=None):
"Just inform that the image can not be broken."
return False # Break is NOT implemented
#===========================================================================
def thanTkGetold(self, proj):
"Gets the attributes of the line interactively from a window."
scale = 0.0254/300 * 500
fildir = thanfiles.thanFilGetFiledir()
while 1:
fi = p_gtkuti.thanGudGetReadFile(proj[2], "*", "Choose image file",
initialdir=fildir)
print "impil.thanTkGet: fi=", fi
if fi == None: return Canc # Image canceled
try:
fr = file(fi, "rb")
except IOError, why:
p_gtkuti.thanGudModalMessage(proj[2], why, "Image file open failed") # (Gu)i (d)ependent
continue
fr.close()
try:
im = Image.open(fi)
except IOError, why:
p_gtkuti.thanGudModalMessage(proj[2], why, "Image file open failed") # (Gu)i (d)ependent
continue
break
width, height = im.size
res = self.__getInsPoint(proj[2])
if res == Canc: return Canc # Image cancelled (no destroy required)
if res == "p": # Image is for pixel counting
x1, y1 = -0.5, -0.5
scale = 1.0
else:
x1, y1 = res
scale = p_gtkuti.thanGudGetPosFloat(proj[2], "Length of 1 pixel in user data units: ", scale)
if scale == Canc: return Canc # Image canceled (no destroy required)
self.thanSet(fi, im, x1, y1, x1+width*scale, y1+height*scale, 0.0)
return True # Image OK
def thanTkGet(self, proj):
"Gets the attributes of the line interactively from a window."
tit = T["Image file open failed"]
fildir = thanfiles.thanFilGetFiledir()
while True:
fi = p_gtkuti.thanGudGetReadFile(proj[2], "*", "Choose image file",
initialdir=fildir)
if fi == None: return Canc # Image canceled
try:
fr = file(fi, "rb")
except IOError, why:
p_gtkuti.thanGudModalMessage(proj[2], why, tit) # (Gu)i (d)ependent
continue
fr.close()
try:
im = Image.open(fi)
except IOError, why:
p_gtkuti.thanGudModalMessage(proj[2], why, tit) # (Gu)i (d)ependent
continue
dxp, dyp = im.size
if dxp < 2 or dyp < 2:
why = T["Image is probably corrupted: size is less than 2 pixels"]
p_gtkuti.thanGudModalMessage(proj[2], why, tit) # (Gu)i (d)ependent
continue
dxp -= 1; dyp -= 1
break
c1 = proj[2].thanGudGetPoint(T["Lower-left image point (pixel/<enter>): "], options=("pixel",))
if c1 == Canc: return Canc # Image cancelled (no destroy required)
if c1 == "p": # Image is for pixel counting
c1 = list(proj[1].thanVar["elevation"])
c1[:2] = 0.0, 0.0 # This is the center of the lower-left pixel
c2 = list(c1)
c2[0] += dxp # This is the center of the upper-right pixel
c2[1] += dyp
else:
c2 = proj[2].thanGudGetRectratio(c1, T["Image width in user data units: "], float(dyp)/dxp)
if c2 == Canc: return Canc # Image canceled (no destroy is required)
self.thanSet(fi, im, c1, c2, theta=0.0)
return True # Image OK
def thanTkDrawold(self, than):
"Draws the image to a window."
than.thanStatusTemp("Regenerating image..")
xa, yb = than.ct.global2Local(self.c1[0], self.c1[1]) # xa, ya is the upper left point of the image
xb, ya = than.ct.global2Local(self.c2[0], self.c2[1]) # xb, yb is the lower right point of the image
item1 = than.dc.create_line(xa, ya, xb, ya, xb, yb, xa, yb, xa, ya, fill=than.outline, tags=self.thanTags) # Frame around image
if isinstance(self.image, ThanImageMissing):
item2 = None
than.thanImages[item1, item2] = self
from thantext import ThanText
t = ThanText()
h = (self.c2[0]-self.c1[0])/40.0
n = len(self.filnam)
xa = (self.c1[0] + self.c2[0])*0.5
ya = (self.c1[1] + self.c2[1])*0.5
t.thanSet(self.filnam, xa-n*h*0.5, ya-h*0.5, h, self.theta)
t.thanTags = self.thanTags
t.thanTkDraw(than)
del t
return
wx = int(xb - xa); wy = int(yb - ya)
assert wx>=0 and wy>=0, "Something wrong with coordinates systems!!!"
if wx*wy > 4000000: # If less than 4Mpixels render the entire image
xymm = than.viewPort
if self.c2[0] < xymm[0] or self.c1[0] > xymm[2] or self.c2[1] < xymm[1] or self.c1[1] > xymm[3]:
assert False, "Image is outside visible screen!"
self.view[0] = xn1 = max(self.c1[0], xymm[0])
self.view[1] = yn1 = max(self.c1[1], xymm[1])
self.view[2] = xn2 = min(self.c2[0], xymm[2])
self.view[3] = yn2 = min(self.c2[1], xymm[3])
for i,v in enumerate((self.c1[0], self.c1[1], self.c2[0], self.c2[1])):
if self.view[i] == v: self.view[i] = None
xa, yb = than.ct.global2Local(xn1, yn1) # xa, ya is the upper left point of the image
xb, ya = than.ct.global2Local(xn2, yn2) # xb, yb is the lower right point of the image
wx = int(xb - xa); wy = int(yb - ya)
assert wx>=0 and wy>=0, "Something wrong with coordinates systems!!!"
assert wx*wy <= 4000000, "Reduced (!!!) image too big: %.0f x %.0f" % (wx,wy)
dxp, dyp = self.size
xp1 = int(linear(self.c1[0], 0, self.c2[0], dxp, xn1)+0.5)
xp2 = int(linear(self.c1[0], 0, self.c2[0], dxp, xn2)+0.5)
yp2 = int(linear(self.c1[1], dyp, self.c2[1], 0, yn1)+0.5)
yp1 = int(linear(self.c1[1], dyp, self.c2[1], 0, yn2)+0.5)
print xn1, yn1, xn2, yn2
print xp1, yp1
print xp2, yp2
self.imagez = self.image.crop((xp1, yp1, xp2, yp2)) # This is a lazy operation (i.e. very fast)
print "imagez before:", self.imagez.size
self.imagez = ImageTk.PhotoImage(self.imagez.resize((wx, wy)))
else:
if self.theta == 0.0: self.imagez = ImageTk.PhotoImage(self.image.resize((wx, wy)))
else: self.imagez = ImageTk.PhotoImage(self.image.resize((wx, wy)).rotate(self.theta))
self.view = [None, None, None, None]
# item2 = win.thanCanvas.create_image(xa, ya, image=self.imagez, anchor="sw", tags=self.thanTags)
item2 = than.dc.create_image(xa, ya, image=self.imagez, anchor="nw", tags=self.thanTags)
than.thanImages[item1, item2] = self
# win.thanStatusUpdate()
def thanTkDraw(self, than):
"Draws the image to a window."
xa, yb = than.ct.global2Locali(self.c1[0], self.c1[1]) # xa, ya is the upper left point of the image
xb, ya = than.ct.global2Locali(self.c2[0], self.c2[1]) # xb, yb is the lower right point of the image
if isinstance(self.image, ThanImageMissing):
item2 = None
# item1 = than.dc.create_line(xa, ya, xb, ya, xb, yb, xa, yb, xa, ya, fill=than.outline, tags=self.thanTags) # Frame around image
item1 = than.dc.create_rectangle(xa, yb, xb, ya, outline=than.outline, tags=self.thanTags) # Frame around image
than.thanImages[item1, item2] = self
from thantext import ThanText
t = ThanText()
h = (self.c2[0]-self.c1[0])/40.0
n = len(self.filnam)
ca = list(self.c1)
ca[0] = (self.c1[0] + self.c2[0])*0.5 - n*h*0.5
ca[1] = (self.c1[1] + self.c2[1])*0.5 - h*0.5
t.thanSet(self.filnam, ca, h, self.theta)
t.thanTags = self.thanTags
t.thanTkDraw(than)
del t
return
than.thanInfoPush(T["Regenerating image.."])
wx = xb - xa; wy = yb - ya
assert wx>=0 and wy>=0, "Something wrong with coordinates systems!!!"
if wx*wy > 4000000: # If less than 4Mpixels render the entire image
xymm = than.viewPort
if self.c2[0] < xymm[0] or self.c1[0] > xymm[2] or self.c2[1] < xymm[1] or self.c1[1] > xymm[3]:
assert False, "Image is outside visible screen!"
self.view[0] = xn1 = max(self.c1[0], xymm[0])
self.view[1] = yn1 = max(self.c1[1], xymm[1])
self.view[2] = xn2 = min(self.c2[0], xymm[2])
self.view[3] = yn2 = min(self.c2[1], xymm[3])
for i,v in enumerate((self.c1[0], self.c1[1], self.c2[0], self.c2[1])):
if self.view[i] == v: self.view[i] = None
xa, yb = than.ct.global2Locali(xn1, yn1) # xa, ya is the upper left point of the image
xb, ya = than.ct.global2Locali(xn2, yn2) # xb, yb is the lower right point of the image
wx = xb-xa; wy = yb - ya
assert wx>=0 and wy>=0, "Something wrong with coordinates systems!!!"
assert wx*wy <= 4000000, "Reduced (!!!) image too big: %.0f x %.0f" % (wx,wy)
xp1, yp2 = self.thanGetPixCoor([xn1, yn1])
xp2, yp1 = self.thanGetPixCoor([xn2, yn2])
print xn1, yn1, xn2, yn2
print xp1, yp1
print xp2, yp2
self.imagez = self.image.crop((xp1, yp1, xp2, yp2)) # This is a lazy operation (i.e. very fast)
print "imagez before:", self.imagez.size
self.imagez = ImageTk.PhotoImage(self.imagez.resize((wx, wy)))
else:
if self.theta == 0.0: self.imagez = ImageTk.PhotoImage(self.image.resize((wx, wy)))
# else: self.imagez = ImageTk.PhotoImage(self.image.resize((wx, wy)).rotate(self.theta*180/pi))
else: self.imagez = ImageTk.PhotoImage(self.image.resize((wx, wy)))
self.view = [None, None, None, None]
item2 = than.dc.create_image(xa, yb, image=self.imagez, anchor="sw", tags=self.thanTags)
# item2 = than.dc.create_image(xa, ya, image=self.imagez, anchor="nw", tags=self.thanTags)
# item1 = than.dc.create_line(xa, ya, xb, ya, xb, yb, xa, yb, xa, ya, fill=than.outline, tags=self.thanTags) # Frame around image
if than.imageFrameOn:
item1 = than.dc.create_rectangle(xa, yb, xb, ya, outline=than.outline, tags=self.thanTags) # Frame around image
else:
item1 = self.thanTags[0] # Just a unique value; nothin special
than.thanImages[item1, item2] = self
than.thanInfoPop()
def thanTkHiwin(self, than):
"Highlights with a (small) window very small elements so that they become visible."
self.thanTkHiwinDo(than, self.thanLength(), self.c1)
#===========================================================================
def thanExpDxf(self, fDxf, level=12):
"Exports the image to dxf file."
if level == 12:
cp = self.__boundaryLine()
cp.append(cp[0])
for c in cp:
fDxf.thanDxfPlotPolyVertex3(c[0], c[1], c[2], 2)
fDxf.thanDxfPlotPolyVertex3(0, 0, 0, 999)
h = (self.c2[0]-self.c1[0])/40.0
n = len(self.filnam)
xa = (self.c1[0] + self.c2[0])*0.5
ya = (self.c1[1] + self.c2[1])*0.5
fDxf.thanDxfPlotSymbol(xa-n*h*0.5, ya-h*0.5, h, self.filnam, self.theta)
else:
wx = fabs(self.c2[0] - self.c1[0])
wy = fabs(self.c2[1] - self.c2[1])
dx, dy = self.size
dx = min(wx/float(dx), wy/float(dy))
fDxf.thanDxfPlotImage(self.filnam, self.c1[0], self.c1[1], self.size, dx, self.theta)
def thanExpSyk(self, than, level=0):
"Exports the image to syk file."
if level == 0: return
cp = self.__boundaryLine()
cp.append(cp[0])
than.write("%15.3f %s\n" % (self.c1[2], than.layname))
for c in cp:
than.write("%15.3f%15.3f\n" % (c[0], c[1]))
than.write("$\n")
def thanExpPil(self, than):
"Exports the image to a PIL raster image."
xymm = than.viewPort
if self.c2[0] < xymm[0] or self.c1[0] > xymm[2] or self.c2[1] < xymm[1] or self.c1[1] > xymm[3]:
return #Image is outside visible screen
if not isinstance(self.image, ThanImageMissing):
xn1 = max(self.c1[0], xymm[0])
yn1 = max(self.c1[1], xymm[1])
xn2 = min(self.c2[0], xymm[2])
yn2 = min(self.c2[1], xymm[3])
xa, yb = than.ct.global2Locali(xn1, yn1) # xa, ya is the upper left point of the image
xb, ya = than.ct.global2Locali(xn2, yn2) # xb, yb is the lower right point of the image
wx = xb - xa; wy = yb - ya
assert wx>=0 and wy>=0, "Something wrong with coordinates systems!!!"
assert wx*wy <= 4000000, "Reduced (!!!) image too big: %.0f x %.0f" % (wx,wy)
dxp, dyp = self.size
xp1, yp2 = self.thanGetPixCoor([xn1, yn1])
xp2, yp1 = self.thanGetPixCoor([xn2, yn2])
imagez = self.image.crop((xp1, yp1, xp2, yp2)) # This is a lazy operation (i.e. very fast)
imagez = imagez.resize((wx, wy))
than.im.paste(imagez, (xa,ya,xb,yb))
if than.imageFrameOn or isinstance(self.image, ThanImageMissing):
cp = self.__boundaryLine()
cp.append(cp[0])
from thandr import ThanLine
elem = ThanLine()
elem.thanSet(cp)
elem.thanExpPil(than)
def thanList(self, than):
"Shows information about the image element."
than.writecom("Element: IMAGE")
than.write(" Layer: % s\n" % than.laypath)
than.write("Length: %s Area: %s\n" % (than.strdis(self.thanLength()), than.strdis(self.thanArea())))
t = ("Filename: %s" % self.filnam,
"Bounding box: %s" % (than.strcoo(self.c1)),
" %s" % (than.strcoo(self.c2)),
"Angle (not used): %s\n"% than.strang(self.theta),
)
than.write("\n".join(t))
#===========================================================================
def thanGetPixCol(self, cw):
"Finds the color of the pixel of the image that corresponds to the world coordinates xw, yw."
return self.image.getpixel(self.thanGetPixCoor(cw))
def __getitem__(self, ji):
"Returns True if the pixel at ji is black (or less than a threshhold value non-b/w images)."
return self.image.getpixel(ji) < 127
def thanGetPixCoor(self, cw):
"Finds the image pixel coordinates that corresponds to the world coordinates xw, yw."
if cw[0]<self.c1[0] or cw[1]<self.c1[1] or cw[0]>self.c2[0] or cw[1]>self.c2[1]:
raise IndexError, "World coordinates do not correspond to image."
dxp, dyp = self.size # Note that dxp,dyp are 1 less than real dimensions
dx, dy = self.c2[0]-self.c1[0], self.c2[1]-self.c1[1]
jx = int( (cw[0]-self.c1[0])/dx*dxp + 0.5) # Note that dxp is one less the width in pixels
iy = int( (self.c2[1]-cw[1])/dy*dyp + 0.5) # Note that dyp is one less the height in pixels
return jx, iy
def thanGetWorldCoor(self, jx, iy, celev=None):
"Finds the the world coordinates that corresponds to the image pixel coordinates xp, yp."
dxp, dyp = self.size
if jx<0 or iy<0 or jx>dxp or iy>dyp: # Note that dxp,dyp are one less than actual
raise IndexError, "Pixel coordinates do not correspond to image"
dx, dy = self.c2[0]-self.c1[0], self.c2[1]-self.c1[1]
if celev == None: cw = list(self.c1)
else: cw = list(celev)
cw[0] = self.c1[0] + jx*dx/dxp # Note that dxp is one less the width in pixels
cw[1] = self.c2[1] - iy*dy/dyp # Note that dyp is one less the height in pixels
return cw
if __name__ == "__main__":
print __doc__
|