def rotateBackPoints(self, xmin, ymin, xmax, ymax, angle): center=QPointF((xmin+xmax)/2,(ymin+ymax)/2) k0=Shape.rotatePoint(self,center,QPointF(xmin,ymin),-angle) k2=Shape.rotatePoint(self,center,QPointF(xmax,ymax),-angle) rotatedPoints=[k0,k2] points= [(int(point.x()),int(point.y())) for point in rotatedPoints] return points
def boundedMoveVertex(self, pos): index, shape = self.hVertex, self.hShape if self.shapeOutOfPixmap(shape.points): #this is pre fix. For bug when shape reach Picmap edge. return if shape.shape3D: # 3D editing OFF return rotatedAxis = False if shape.deg > 0 and not shape.tetragon: rotatedAxis = True shape.points = self.getRotatedShape(shape, -shape.deg) pos = Shape.rotatePoint(self, shape.centerPoint, pos, -shape.deg) point = shape[index] if self.outOfPixmap(pos): if self.shapeOutOfPixmap(shape.points) == False: pos = self.intersectionPoint(point, pos) shiftPos = pos - point shape.moveVertexBy(index, shiftPos) lindex = (index + 1) % 4 rindex = (index + 3) % 4 lshift = None rshift = None if index % 2 == 0: #lyginiai index if shape.tetragon == True: rshift = QPointF(0, 0) lshift = QPointF(0, 0) elif shape.tetragon == False: rshift = QPointF(shiftPos.x(), 0) lshift = QPointF(0, shiftPos.y()) else: #nelyginiai index if shape.tetragon == True: lshift = QPointF(0, 0) rshift = QPointF(0, 0) elif shape.tetragon == False: lshift = QPointF(shiftPos.x(), 0) rshift = QPointF(0, shiftPos.y()) shape.moveVertexBy(rindex, rshift) shape.moveVertexBy(lindex, lshift) if rotatedAxis: shape.points = self.getRotatedShape(shape, shape.deg) pos = Shape.rotatePoint(self, shape.centerPoint, pos, shape.deg)
def resizeShape(self, pos, index, shape): """ Resize shape of rectangle, enforcing rectangular form in the original coordinates """ dot = lambda x, y: x.x() * y.x() + x.y() * y.y() eucl = lambda a: 1. * a.x()**2 + 1. * a.y()**2 rot = lambda p, a: Shape.rotatePoint(p, QPointF(0, 0), a) rotShapeLine = lambda i, ia, s: rot( s.pointsWithoutRotation[ia] - s.pointsWithoutRotation[i], s. currentAngle) if shape.points[index] != pos: # give reasonable names to the vectices that are affected ('left' # and 'right' and to the vertex that remains unaffected 'other') rindex, lindex, oindex = [(index + o) % 4 for o in [1, 3, 2]] # A) compute the offset defining the movement to be applied in this # step at the vertex in question. pos = self.getClosestValid(pos) offset_rotated = shape.points[index] - pos shape.points[index] = pos # B) Find new location of affected points (lindex and rindex) # 1) w,h = rotate back vector from dragged vertex (=: i) to other # vertex (=: o) # 2) cos(w or h), sin(w or h) -> vector from i to lindex (=:l) # or rindex (=: r) vec_involved = shape.points[oindex] - shape.points[index] size = -rot(vec_involved, -shape.currentAngle) w, h = size.x(), size.y() vec_il = QPointF( cos(shape.currentAngle) * w, sin(shape.currentAngle) * w) vec_ir = QPointF(-sin(shape.currentAngle) * h, cos(shape.currentAngle) * h) # C) Correct locations accordingly # 1) compute position 1, 3 # 2) compute intersection in rotated space, such that it is # ensured that the resulting values are rounded inside the # coordinates. # Subtract the resulting value directly from i and 1 or 3 # 3) Use the projection vector to move both the currently # dragged point and the point that is out of bounds to the # last valid location. if index % 2 == 0: rind = vec_ir vec_ir = vec_il vec_il = rind shape.points[rindex] = shape.points[index] - vec_ir shape.points[lindex] = shape.points[index] - vec_il # apply the rotation angle to the data that is uk shape_center = shape.getCenter(rotated=True) shifts = self.checkBorders(shape, index, lindex, vec_ir), \ self.checkBorders(shape, index, rindex, vec_il) for shift in shifts: if shift is not None: shape.points[index] += shift # apply the new coordinates to the latent (unrotated) array shape.applyRotationAngle(shape.currentAngle, shape_center, False)
def getRotatedShape(self, shape, angle): return [ Shape.rotatePoint(self, shape.centerPoint, point, angle) for point in shape.points ]
def rotateShape(self, pos, shape, debug=True): """ Rotates a shape by dragging the shape-rotation-button to the position `pos`. Checks if the resulting shape is completely inside the image in the image. If not, rotate by an angle that is closest to the desired angle but still yielding a shape inside the image. """ # Case 1: Rotate the shape vertex_not_rotated = shape.getShapeRotationVertex(False) if vertex_not_rotated is not None: eucl_sq = lambda a: a.x()**2 + a.y()**2 # Fetch the original (=not rotated) vertex-position for movement # and the center of mass of the shape (once again according to # the coordinates that are not rotated) vertex_point = vertex_not_rotated[0] vertex_mirrored = vertex_not_rotated[2] shape_center = shape.getCenter(False) # Compute the vector and distance between both aforementioned points vec_old_center = vertex_point - shape_center # - vertex_point dist_old_center_square = eucl_sq(vec_old_center) # Compute the vector and distance between the new position and the center vec_new_center = pos - shape_center # - pos dist_new_center_square = eucl_sq(vec_new_center) # Now compute the angle between both the vector pointing to the new # position of the rotation vertex and the one pointing to its # original position. # Make completely sure that no rounding errors can cause # mathematical errors for the input value by checking bounds. val = QPointF.dotProduct(vec_new_center, vec_old_center) / \ (dist_new_center_square * dist_old_center_square) **.5 val = min(max(val, -1), 1) angle = acos(val) # The direction of movement has to be adapted depending on the # current state of the vertex. # First condition: vertex is 'mirrored': # the initially topmost line is dragged under line # at the bottom; In this case the sign must be # swapped. # Second condition: as the shape-move vertex is always # directly above or beneath the shape's center # it is succicent to check the x coordinate for # checking if the rotation is 'in the second # half'. In that case, rotate by 2pi -angle transform_angle = lambda a, posx : \ (-1 if vertex_mirrored else 1) \ * (a if (posx >= vertex_point.x()) else 2. * pi - a) angle = transform_angle(angle, pos.x()) # XXX: this checking mechanism does not work entirely (the # distinction of valid angles sometimes does not recognize the # fact that two edges are outside the valid area). # and contains debug code (that inserts vertices to some # positions for debugging) and thus should only be commented # in for finishing the implementation of that feature (in case # it is required)). # If it is not required it should be removed. performCheckOfIntervals = False if performCheckOfIntervals: # Not all angles are valid. Find out which angles are leading to # coordiantes outside the image: # Step 1) find (x,y) with \|(x,y) - c \| = \|x_1 - x_3\| # and (x,y) on image's borders # Step 2) find the associated rotation angles and store them # in a sorted way width, height = self.pixmap.width(), self.pixmap.height() # get the radius of the circle p_c = shape.pointsWithoutRotation[0] - shape_center len_p_c = eucl_sq(p_c) # list all the support vectors indicating image border alongside # with their directions support_direction = [ [QPointF(0, 0), QPointF(width - 1, 0)], [QPointF(0, 0), QPointF(0, height - 1)], [QPointF(width - 1, 0), QPointF(0, height - 1)], [QPointF(0, height - 1), QPointF(width - 1, 0)] ] forbiddenAngleIntervals = [] for s, d in support_direction: # find intersections between the circle (defined by the center # and its radius) and the currently considered image border. # # In case there is only one (or none) intersection, # no conditions are imposed in this step on the anlge as the # image borders are selected to be the last line of pixels # inside the image. # # If there are two intersections, the space in between them is # forbidden intersects = Canvas.intersectionLineCircle( s - shape_center, d, sqrt(len_p_c)) if intersects is not None: # In case debugging is enabled, add new shapes that show # the intersections with the borders in the image. # Attention: debugging cannot be used in a productive mode. # Results in a bunch of new vertices. if debug: deb = Shape() deb.addPoint(intersects[0] + shape_center) deb.addPoint(intersects[1] + shape_center) deb.close() self.shapes.append(deb) deb = Shape() deb.addPoint(p_c + shape_center) deb.close() self.shapes.append(deb) # the corresponding angle is the angle between the # intersection point and the vertex_point (shifted by # center) if len(intersects) == 2: angles = [[ transform_angle( acos( QPointF.dotProduct( spwr - shape_center, a) / (len_p_c * eucl_sq(a))**.5), spwr.x()) for a in intersects ] for spwr in shape.pointsWithoutRotation] for i, (a, b) in enumerate(angles): # find the min and max value and compute the # min and max value that are still allowed. # if the angle might be affected by them t = 0 if a < 0: a += 2 * pi if b < 0: b += 2 * pi mx, mi = max(a, b), min(a, b) if mx - mi > pi: forbiddenAngleIntervals.append( [mx, 2 * pi]) forbiddenAngleIntervals.append([0, mi]) else: forbiddenAngleIntervals.append([mi, mx]) #if a < b: # forbiddenAngleIntervals.append([a, b]) #elif b < a: # forbiddenAngleIntervals.append([a, 2*pi]) # forbiddenAngleIntervals.append([0, b]) # paint vector (forbidden area) based on the # computed angle if debug: p1 = Shape.rotatePoint( shape.pointsWithoutRotation[i], shape_center, a) p2 = Shape.rotatePoint( shape.pointsWithoutRotation[i], shape_center, b) deb = Shape() deb.addPoint(p1) deb.addPoint(p2) deb.close() self.shapes.append(deb) # XXX: There most likely is a better solution to this. # The code below is supposed to unite all forbidden intervals. # This is necessary for being able to pick the closest point # to the forbidden area. if len(forbiddenAngleIntervals): unionInterval = [forbiddenAngleIntervals[0]] uiid = 0 # starts before other.end and stops after other.start checkIntersect = lambda a, b: a[1] >= b[0] and a[0] <= b[1] checkIntersectMutual = lambda a, b: checkIntersect(a, b) \ or checkIntersect(b, a) # need to check multiple times as there might be an array that # unites two other arrays. for k in range(len(forbiddenAngleIntervals) - 1): for i in range(1, len(forbiddenAngleIntervals)): # check if there is already is an interval comprising me inters = False for ui in range(len(unionInterval)): # end union > start this if (checkIntersectMutual( unionInterval[ui], forbiddenAngleIntervals[i])): unionInterval[ui][0] = min( unionInterval[ui][0], forbiddenAngleIntervals[i][0]) unionInterval[ui][1] = max( unionInterval[ui][1], forbiddenAngleIntervals[i][1]) inters = True break if not inters: unionInterval.append( forbiddenAngleIntervals[i]) print(forbiddenAngleIntervals, unionInterval) # Check if there is some intersection and use the closest point # as corrected angle. if angle < 0: angle += 2 * pi for i in unionInterval: if i[0] < angle and angle < i[1]: angle = i[0] if angle - i[0] < i[1] - angle else i[ 1] break # Apply the rotation for the shape (computes new location of rotated # values and stores the current angle for future reference): shape.applyRotationAngle(angle, shape_center)