From 5d524e4faad977e9e62ad478990750a502ef276f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bia=C5=82ow=C4=85s?= Date: Sun, 23 Feb 2025 21:31:03 +0100 Subject: [PATCH 1/8] Turtle custom ShapeDrawer mechanism added --- Lib/turtle.py | 60 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/Lib/turtle.py b/Lib/turtle.py index e88981d298ad52..fb7d7a0aa56277 100644 --- a/Lib/turtle.py +++ b/Lib/turtle.py @@ -860,13 +860,26 @@ class TurtleGraphicsError(Exception): """Some TurtleGraphics Error """ +class BaseShapeDrawer(object): + """Base class that allows for taking over controll of drawing process + for custom turtle shapes + """ + def __init__(self, screen): + self.screen = screen + def clone(self, *args, **kwargs): + return self.__class__(self.screen, *args, **kwargs) + def draw(self, position, orientation, pen_color, fill_color, transform, pen_size): + pass + def delete(self): + pass class Shape(object): """Data structure modeling shapes. - attribute _type is one of "polygon", "image", "compound" + attribute _type is one of "polygon", "image", "compound", "shape_drawer" attribute _data is - depending on _type a poygon-tuple, - an image or a list constructed using the addcomponent method. + an image, a list constructed using the addcomponent method or a subclass of + BaseShapeDrawer. """ def __init__(self, type_, data=None): self._type = type_ @@ -875,6 +888,8 @@ def __init__(self, type_, data=None): data = tuple(data) elif type_ == "image": assert(isinstance(data, TK.PhotoImage)) + elif type_ == "shape_drawer": + assert(issubclass(data, BaseShapeDrawer)) elif type_ == "compound": data = [] else: @@ -1111,8 +1126,8 @@ def register_shape(self, name, shape=None): of pairs of coordinates. Installs the corresponding polygon shape (4) name is an arbitrary string and shape is a - (compound) Shape object. Installs the corresponding - compound shape. + (compound or shape_drawer) Shape object. Installs the corresponding + shape. To use a shape, you have to issue the command shape(shapename). call: register_shape("turtle.gif") @@ -2550,7 +2565,6 @@ def __init__(self, screen, shapeIndex): def _setshape(self, shapeIndex): screen = self.screen - self.shapeIndex = shapeIndex if self._type == "polygon" == screen._shapes[shapeIndex]._type: return if self._type == "image" == screen._shapes[shapeIndex]._type: @@ -2560,7 +2574,12 @@ def _setshape(self, shapeIndex): elif self._type == "compound": for item in self._item: screen._delete(item) + elif self._type == "shape_drawer": + del self._item + self._type = screen._shapes[shapeIndex]._type + self.shapeIndex = shapeIndex + if self._type == "polygon": self._item = screen._createpoly() elif self._type == "image": @@ -2568,6 +2587,8 @@ def _setshape(self, shapeIndex): elif self._type == "compound": self._item = [screen._createpoly() for item in screen._shapes[shapeIndex]._data] + elif self._type == "shape_drawer": + self._item = screen._shapes[self.shapeIndex]._data(screen) class RawTurtle(TPen, TNavigator): @@ -2855,6 +2876,8 @@ def clone(self): elif ttype == "compound": q.turtle._item = [screen._createpoly() for item in screen._shapes[self.turtle.shapeIndex]._data] + elif ttype == "shape_drawer": + q.turtle._item = screen._shapes[self.turtle.shapeIndex]._data(screen) q.currentLineItem = screen._createline() q._update() return q @@ -3111,6 +3134,10 @@ def _drawturtle(self): poly = self._polytrafo(self._getshapepoly(poly, True)) screen._drawpoly(item, poly, fill=self._cc(fc), outline=self._cc(oc), width=self._outlinewidth, top=True) + elif ttype == "shape_drawer": + titem.draw(self._position, self._orient, pen_color = self._pencolor, + fill_color = self._fillcolor, transform = self._shapetrafo, + pen_size = self._pensize) else: if self._hidden_from_screen: return @@ -3167,6 +3194,11 @@ def stamp(self): poly = self._polytrafo(self._getshapepoly(poly, True)) screen._drawpoly(item, poly, fill=self._cc(fc), outline=self._cc(oc), width=self._outlinewidth, top=True) + elif ttype == "shape_drawer": + stitem = self.turtle._item.clone() + stitem.draw(self._position, self._orient, pen_color = self._pencolor, + fill_color = self._fillcolor, transform = self._shapetrafo, + pen_size = self._pensize) self.stampItems.append(stitem) self.undobuffer.push(("stamp", stitem)) return stitem @@ -3178,20 +3210,22 @@ def _clearstamp(self, stampid): if isinstance(stampid, tuple): for subitem in stampid: self.screen._delete(subitem) - else: + elif not isinstance(stampid, BaseShapeDrawer): self.screen._delete(stampid) self.stampItems.remove(stampid) + # Delete stampitem from undobuffer if necessary # if clearstamp is called directly. item = ("stamp", stampid) buf = self.undobuffer - if item not in buf.buffer: - return - index = buf.buffer.index(item) - buf.buffer.remove(item) - if index <= buf.ptr: - buf.ptr = (buf.ptr - 1) % buf.bufsize - buf.buffer.insert((buf.ptr+1)%buf.bufsize, [None]) + if item in buf.buffer: + index = buf.buffer.index(item) + buf.buffer.remove(item) + if index <= buf.ptr: + buf.ptr = (buf.ptr - 1) % buf.bufsize + buf.buffer.insert((buf.ptr+1)%buf.bufsize, [None]) + if isinstance(stampid, BaseShapeDrawer): + stampid.delete() def clearstamp(self, stampid): """Delete stamp with given stampid From 33339067a302b773a06f04ca506b5e84332cbe47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bia=C5=82ow=C4=85s?= Date: Thu, 27 Feb 2025 21:12:29 +0100 Subject: [PATCH 2/8] Added rotation code to image so it doesn't expose internals anymore --- Lib/turtle.py | 172 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 141 insertions(+), 31 deletions(-) diff --git a/Lib/turtle.py b/Lib/turtle.py index fb7d7a0aa56277..076fa712609233 100644 --- a/Lib/turtle.py +++ b/Lib/turtle.py @@ -860,26 +860,140 @@ class TurtleGraphicsError(Exception): """Some TurtleGraphics Error """ -class BaseShapeDrawer(object): - """Base class that allows for taking over controll of drawing process - for custom turtle shapes - """ - def __init__(self, screen): - self.screen = screen +class TransformableImage(object): + """Class that handles rotation of image based turtle shape""" + def __init__(self, screen, image): + assert(isinstance(image, TK.PhotoImage)) + self._screen = screen + self._originalImage = image + self._rotationCenter = (image.width() / 2, image.height() / 2) + self._tkPhotoImage = image.copy() + self._currentOrientation = (1.0, 0) + self._currentTilt = 0.0 + self._transformMatrix = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] + self._item = screen._createimage(self._tkPhotoImage) + def clone(self, *args, **kwargs): - return self.__class__(self.screen, *args, **kwargs) - def draw(self, position, orientation, pen_color, fill_color, transform, pen_size): - pass + return self.__class__(self._screen, self._originalImage, *args, **kwargs) + + def _transform_coordinates(self, x, y): + m = self._transformMatrix + return (m[0][0] * x + m[0][1] * y + m[0][2], + m[1][0] * x + m[1][1] * y + m[1][2]) + + def _get_new_bounding_box(self): + w, h = self._originalImage.width(), self._originalImage.height() + c = [self._transform_coordinates(x, y) + for x, y in [(0, 0), (w, 0), (w, h), (0, h)]] + min_x = min(x for x, _ in c) + min_y = min(y for _, y in c) + max_x = max(x for x, _ in c) + max_y = max(y for _, y in c) + return { + "min_x": min_x, + "min_y": min_y, + "max_x": max_x, + "max_y": max_y, + "width": math.ceil(max_x - min_x), + "height": math.ceil(max_y - min_y) + } + + def _interpolate_color(self, x, y): + """Interpolates color based on neighboring pixels.""" + x_floor, y_floor = int(x), int(y) + x_ceil, y_ceil = math.ceil(x), math.ceil(y) + + if x_floor == x_ceil and y_floor == y_ceil: + return self._originalImage.get(x_floor, y_floor) + + if x_floor == x_ceil: + c1 = self._originalImage.get(x_floor, y_floor) + c2 = self._originalImage.get(x_floor, y_ceil) + alpha = y - y_floor + return ( + int(c1[0] * (1 - alpha) + c2[0] * alpha), + int(c1[1] * (1 - alpha) + c2[1] * alpha), + int(c1[2] * (1 - alpha) + c2[2] * alpha), + ) + + if y_floor == y_ceil: + c1 = self._originalImage.get(x_floor, y_floor) + c2 = self._originalImage.get(x_ceil, y_floor) + alpha = x - x_floor + return ( + int(c1[0] * (1 - alpha) + c2[0] * alpha), + int(c1[1] * (1 - alpha) + c2[1] * alpha), + int(c1[2] * (1 - alpha) + c2[2] * alpha), + ) + + c11 = self._originalImage.get(x_floor, y_floor) + c12 = self._originalImage.get(x_floor, y_ceil) + c21 = self._originalImage.get(x_ceil, y_floor) + c22 = self._originalImage.get(x_ceil, y_ceil) + + alpha_x = x - x_floor + alpha_y = y - y_floor + + c1 = ( + int(c11[0] * (1 - alpha_y) + c12[0] * alpha_y), + int(c11[1] * (1 - alpha_y) + c12[1] * alpha_y), + int(c11[2] * (1 - alpha_y) + c12[2] * alpha_y), + ) + c2 = ( + int(c21[0] * (1 - alpha_y) + c22[0] * alpha_y), + int(c21[1] * (1 - alpha_y) + c22[1] * alpha_y), + int(c21[2] * (1 - alpha_y) + c22[2] * alpha_y), + ) + + return ( + int(c1[0] * (1 - alpha_x) + c2[0] * alpha_x), + int(c1[1] * (1 - alpha_x) + c2[1] * alpha_x), + int(c1[2] * (1 - alpha_x) + c2[2] * alpha_x), + ) + + def draw(self, position, orientation, tilt): + if (self._currentOrientation != orientation or self._currentTilt != tilt): + angle = math.atan2(orientation[1], orientation[0]) + tilt + cos_theta = math.cos(angle) + sin_theta = math.sin(angle) + x, y = self._rotationCenter + self._transformMatrix = [ + [cos_theta, -sin_theta, x * (1 - cos_theta) + y * sin_theta], + [sin_theta, cos_theta, y * (1 - cos_theta) - x * sin_theta], + [0, 0, 1] + ] + bounding_box = self._get_new_bounding_box() + offset_x = bounding_box["min_x"] * -1 + offset_y = bounding_box["min_y"] * -1 + self._tkPhotoImage = TK.PhotoImage(width=bounding_box["width"], height=bounding_box["height"]) + + for new_y in range(bounding_box["height"]): + for new_x in range(bounding_box["width"]): + original_x, original_y = self._transform_coordinates(new_x - offset_x, new_y - offset_y) + + if 0 <= original_x < self._originalImage.width() - 1 and 0 <= original_y < self._originalImage.height() - 1: + rgb = self._interpolate_color(original_x, original_y) + is_transparent = self._originalImage.transparency_get(int(original_x), int(original_y)) + self._tkPhotoImage.put("#{:02x}{:02x}{:02x}".format(rgb[0], rgb[1], rgb[2]), (new_x, new_y)) + self._tkPhotoImage.transparency_set(new_x, new_y, is_transparent) + elif 0 <= int(original_x) < self._originalImage.width() and 0 <= int(original_y) < self._originalImage.height(): + rgb = self._originalImage.get(int(original_x),int(original_y)) + is_transparent = self._originalImage.transparency_get(int(original_x), int(original_y)) + self._tkPhotoImage.put("#{:02x}{:02x}{:02x}".format(rgb[0], rgb[1], rgb[2]), (new_x, new_y)) + self._tkPhotoImage.transparency_set(new_x, new_y, is_transparent) + self._currentOrientation = orientation + self._currentTilt = tilt + self._screen._drawimage(self._item, position, self._tkPhotoImage) + def delete(self): - pass + self._screen._delete(self._item) class Shape(object): """Data structure modeling shapes. - attribute _type is one of "polygon", "image", "compound", "shape_drawer" + attribute _type is one of "polygon", "image", "compound", "transformable_image" attribute _data is - depending on _type a poygon-tuple, - an image, a list constructed using the addcomponent method or a subclass of - BaseShapeDrawer. + an image or a list constructed using the addcomponent method """ def __init__(self, type_, data=None): self._type = type_ @@ -888,8 +1002,8 @@ def __init__(self, type_, data=None): data = tuple(data) elif type_ == "image": assert(isinstance(data, TK.PhotoImage)) - elif type_ == "shape_drawer": - assert(issubclass(data, BaseShapeDrawer)) + elif type_ == "transformable_image": + assert(isinstance(data, TK.PhotoImage)) elif type_ == "compound": data = [] else: @@ -1126,7 +1240,7 @@ def register_shape(self, name, shape=None): of pairs of coordinates. Installs the corresponding polygon shape (4) name is an arbitrary string and shape is a - (compound or shape_drawer) Shape object. Installs the corresponding + (compound or transformable_image) Shape object. Installs the corresponding shape. To use a shape, you have to issue the command shape(shapename). @@ -2574,7 +2688,7 @@ def _setshape(self, shapeIndex): elif self._type == "compound": for item in self._item: screen._delete(item) - elif self._type == "shape_drawer": + elif self._type == "transformable_image": del self._item self._type = screen._shapes[shapeIndex]._type @@ -2587,8 +2701,8 @@ def _setshape(self, shapeIndex): elif self._type == "compound": self._item = [screen._createpoly() for item in screen._shapes[shapeIndex]._data] - elif self._type == "shape_drawer": - self._item = screen._shapes[self.shapeIndex]._data(screen) + elif self._type == "transformable_image": + self._item = TransformableImage(screen, screen._shapes[self.shapeIndex]._data) class RawTurtle(TPen, TNavigator): @@ -2876,8 +2990,8 @@ def clone(self): elif ttype == "compound": q.turtle._item = [screen._createpoly() for item in screen._shapes[self.turtle.shapeIndex]._data] - elif ttype == "shape_drawer": - q.turtle._item = screen._shapes[self.turtle.shapeIndex]._data(screen) + elif ttype == "transformable_image": + q.turtle._item = TransformableImage(screen, screen._shapes[self.turtle.shapeIndex]._data) q.currentLineItem = screen._createline() q._update() return q @@ -3134,10 +3248,8 @@ def _drawturtle(self): poly = self._polytrafo(self._getshapepoly(poly, True)) screen._drawpoly(item, poly, fill=self._cc(fc), outline=self._cc(oc), width=self._outlinewidth, top=True) - elif ttype == "shape_drawer": - titem.draw(self._position, self._orient, pen_color = self._pencolor, - fill_color = self._fillcolor, transform = self._shapetrafo, - pen_size = self._pensize) + elif ttype == "transformable_image": + titem.draw(self._position, self._orient, self._tilt) else: if self._hidden_from_screen: return @@ -3194,11 +3306,9 @@ def stamp(self): poly = self._polytrafo(self._getshapepoly(poly, True)) screen._drawpoly(item, poly, fill=self._cc(fc), outline=self._cc(oc), width=self._outlinewidth, top=True) - elif ttype == "shape_drawer": + elif ttype == "transformable_image": stitem = self.turtle._item.clone() - stitem.draw(self._position, self._orient, pen_color = self._pencolor, - fill_color = self._fillcolor, transform = self._shapetrafo, - pen_size = self._pensize) + stitem.draw(self._position, self._orient, self._tilt) self.stampItems.append(stitem) self.undobuffer.push(("stamp", stitem)) return stitem @@ -3210,7 +3320,7 @@ def _clearstamp(self, stampid): if isinstance(stampid, tuple): for subitem in stampid: self.screen._delete(subitem) - elif not isinstance(stampid, BaseShapeDrawer): + elif not isinstance(stampid, TransformableImage): self.screen._delete(stampid) self.stampItems.remove(stampid) @@ -3224,7 +3334,7 @@ def _clearstamp(self, stampid): if index <= buf.ptr: buf.ptr = (buf.ptr - 1) % buf.bufsize buf.buffer.insert((buf.ptr+1)%buf.bufsize, [None]) - if isinstance(stampid, BaseShapeDrawer): + if isinstance(stampid, TransformableImage): stampid.delete() def clearstamp(self, stampid): From deb3fe049a38d5cd0b7040185665a47a5d2dea53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bia=C5=82ow=C4=85s?= Date: Sat, 1 Mar 2025 00:48:08 +0100 Subject: [PATCH 3/8] add shape modified to handle rotate_image_shape parameter --- Lib/turtle.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Lib/turtle.py b/Lib/turtle.py index 076fa712609233..8c6092680dc6b6 100644 --- a/Lib/turtle.py +++ b/Lib/turtle.py @@ -1224,7 +1224,7 @@ def setworldcoordinates(self, llx, lly, urx, ury): self._rescale(self.xscale/oldxscale, self.yscale/oldyscale) self.update() - def register_shape(self, name, shape=None): + def register_shape(self, name, shape=None, rotate_image_shape=False): """Adds a turtle shape to TurtleScreen's shapelist. Arguments: @@ -1236,6 +1236,7 @@ def register_shape(self, name, shape=None): Installs the corresponding image shape. !! Image-shapes DO NOT rotate when turning the turtle, !! so they do not display the heading of the turtle! + !! unless rotate_image_shape is explicitly set to true (3) name is an arbitrary string and shape is a tuple of pairs of coordinates. Installs the corresponding polygon shape @@ -1246,15 +1247,17 @@ def register_shape(self, name, shape=None): call: register_shape("turtle.gif") --or: register_shape("tri", ((0,0), (10,10), (-10,10))) + --or: register_shape("turtle.gif", rotate_image_shape=True) Example (for a TurtleScreen instance named screen): >>> screen.register_shape("triangle", ((5,-3),(0,5),(-5,-3))) """ + image_shape_type = "transformable_image" if rotate_image_shape else "image" if shape is None: - shape = Shape("image", self._image(name)) + shape = Shape(image_shape_type, self._image(name)) elif isinstance(shape, str): - shape = Shape("image", self._image(shape)) + shape = Shape(image_shape_type, self._image(shape)) elif isinstance(shape, tuple): shape = Shape("polygon", shape) ## else shape assumed to be Shape-instance From 936c50eeabae86696e8a09e20a48eae36041ca82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bia=C5=82ow=C4=85s?= Date: Tue, 4 Mar 2025 21:15:56 +0100 Subject: [PATCH 4/8] formatting/docs fixed --- Lib/turtle.py | 58 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/Lib/turtle.py b/Lib/turtle.py index 8c6092680dc6b6..c4cb09c681fd7c 100644 --- a/Lib/turtle.py +++ b/Lib/turtle.py @@ -965,22 +965,47 @@ def draw(self, position, orientation, tilt): bounding_box = self._get_new_bounding_box() offset_x = bounding_box["min_x"] * -1 offset_y = bounding_box["min_y"] * -1 - self._tkPhotoImage = TK.PhotoImage(width=bounding_box["width"], height=bounding_box["height"]) + self._tkPhotoImage = TK.PhotoImage(width=bounding_box["width"], + height=bounding_box["height"]) for new_y in range(bounding_box["height"]): for new_x in range(bounding_box["width"]): - original_x, original_y = self._transform_coordinates(new_x - offset_x, new_y - offset_y) - - if 0 <= original_x < self._originalImage.width() - 1 and 0 <= original_y < self._originalImage.height() - 1: + original_x, original_y = self._transform_coordinates( + new_x - offset_x, new_y - offset_y + ) + if ( + 0 <= original_x < self._originalImage.width() - 1 + and 0 <= original_y < self._originalImage.height() - 1 + ): rgb = self._interpolate_color(original_x, original_y) - is_transparent = self._originalImage.transparency_get(int(original_x), int(original_y)) - self._tkPhotoImage.put("#{:02x}{:02x}{:02x}".format(rgb[0], rgb[1], rgb[2]), (new_x, new_y)) - self._tkPhotoImage.transparency_set(new_x, new_y, is_transparent) - elif 0 <= int(original_x) < self._originalImage.width() and 0 <= int(original_y) < self._originalImage.height(): - rgb = self._originalImage.get(int(original_x),int(original_y)) - is_transparent = self._originalImage.transparency_get(int(original_x), int(original_y)) - self._tkPhotoImage.put("#{:02x}{:02x}{:02x}".format(rgb[0], rgb[1], rgb[2]), (new_x, new_y)) - self._tkPhotoImage.transparency_set(new_x, new_y, is_transparent) + is_transparent = self._originalImage.transparency_get( + int(original_x), int(original_y) + ) + self._tkPhotoImage.put( + "#{:02x}{:02x}{:02x}".format(rgb[0], rgb[1], rgb[2]), + (new_x, new_y), + ) + self._tkPhotoImage.transparency_set( + new_x, new_y, is_transparent + ) + elif ( + 0 <= int(original_x) < self._originalImage.width() + and 0 <= int(original_y) < self._originalImage.height() + ): + rgb = self._originalImage.get( + int(original_x), int(original_y) + ) + is_transparent = self._originalImage.transparency_get( + int(original_x), int(original_y) + ) + self._tkPhotoImage.put( + "#{:02x}{:02x}{:02x}".format(rgb[0], rgb[1], rgb[2]), + (new_x, new_y), + ) + self._tkPhotoImage.transparency_set( + new_x, new_y, is_transparent + ) + self._currentOrientation = orientation self._currentTilt = tilt self._screen._drawimage(self._item, position, self._tkPhotoImage) @@ -1000,9 +1025,7 @@ def __init__(self, type_, data=None): if type_ == "polygon": if isinstance(data, list): data = tuple(data) - elif type_ == "image": - assert(isinstance(data, TK.PhotoImage)) - elif type_ == "transformable_image": + elif type_ == "image" or type_ == "transformable_image": assert(isinstance(data, TK.PhotoImage)) elif type_ == "compound": data = [] @@ -1241,8 +1264,8 @@ def register_shape(self, name, shape=None, rotate_image_shape=False): of pairs of coordinates. Installs the corresponding polygon shape (4) name is an arbitrary string and shape is a - (compound or transformable_image) Shape object. Installs the corresponding - shape. + (compound) Shape object. Installs the corresponding + compound shape. To use a shape, you have to issue the command shape(shapename). call: register_shape("turtle.gif") @@ -3326,7 +3349,6 @@ def _clearstamp(self, stampid): elif not isinstance(stampid, TransformableImage): self.screen._delete(stampid) self.stampItems.remove(stampid) - # Delete stampitem from undobuffer if necessary # if clearstamp is called directly. item = ("stamp", stampid) From cfcced69f379fdef0a57b35d1ca357ccf70dc2cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bia=C5=82ow=C4=85s?= Date: Tue, 4 Mar 2025 21:37:55 +0100 Subject: [PATCH 5/8] fixed setshape method --- Lib/turtle.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Lib/turtle.py b/Lib/turtle.py index c4cb09c681fd7c..513f8ae5542064 100644 --- a/Lib/turtle.py +++ b/Lib/turtle.py @@ -2705,6 +2705,7 @@ def __init__(self, screen, shapeIndex): def _setshape(self, shapeIndex): screen = self.screen + self.shapeIndex = shapeIndex if self._type == "polygon" == screen._shapes[shapeIndex]._type: return if self._type == "image" == screen._shapes[shapeIndex]._type: @@ -2715,11 +2716,8 @@ def _setshape(self, shapeIndex): for item in self._item: screen._delete(item) elif self._type == "transformable_image": - del self._item - + self._item.delete() self._type = screen._shapes[shapeIndex]._type - self.shapeIndex = shapeIndex - if self._type == "polygon": self._item = screen._createpoly() elif self._type == "image": From e256b327afd14f0afd8e51bf488556a5111092dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bia=C5=82ow=C4=85s?= Date: Tue, 4 Mar 2025 21:59:04 +0100 Subject: [PATCH 6/8] fixed naming in TransformableImage class --- Lib/turtle.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Lib/turtle.py b/Lib/turtle.py index 513f8ae5542064..9bd32bbc81f635 100644 --- a/Lib/turtle.py +++ b/Lib/turtle.py @@ -867,11 +867,11 @@ def __init__(self, screen, image): self._screen = screen self._originalImage = image self._rotationCenter = (image.width() / 2, image.height() / 2) - self._tkPhotoImage = image.copy() + self._transformedImage = image.copy() self._currentOrientation = (1.0, 0) self._currentTilt = 0.0 self._transformMatrix = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] - self._item = screen._createimage(self._tkPhotoImage) + self._item = screen._createimage(self._transformedImage) def clone(self, *args, **kwargs): return self.__class__(self._screen, self._originalImage, *args, **kwargs) @@ -965,7 +965,7 @@ def draw(self, position, orientation, tilt): bounding_box = self._get_new_bounding_box() offset_x = bounding_box["min_x"] * -1 offset_y = bounding_box["min_y"] * -1 - self._tkPhotoImage = TK.PhotoImage(width=bounding_box["width"], + self._transformedImage = TK.PhotoImage(width=bounding_box["width"], height=bounding_box["height"]) for new_y in range(bounding_box["height"]): @@ -981,11 +981,11 @@ def draw(self, position, orientation, tilt): is_transparent = self._originalImage.transparency_get( int(original_x), int(original_y) ) - self._tkPhotoImage.put( + self._transformedImage.put( "#{:02x}{:02x}{:02x}".format(rgb[0], rgb[1], rgb[2]), (new_x, new_y), ) - self._tkPhotoImage.transparency_set( + self._transformedImage.transparency_set( new_x, new_y, is_transparent ) elif ( @@ -998,17 +998,17 @@ def draw(self, position, orientation, tilt): is_transparent = self._originalImage.transparency_get( int(original_x), int(original_y) ) - self._tkPhotoImage.put( + self._transformedImage.put( "#{:02x}{:02x}{:02x}".format(rgb[0], rgb[1], rgb[2]), (new_x, new_y), ) - self._tkPhotoImage.transparency_set( + self._transformedImage.transparency_set( new_x, new_y, is_transparent ) self._currentOrientation = orientation self._currentTilt = tilt - self._screen._drawimage(self._item, position, self._tkPhotoImage) + self._screen._drawimage(self._item, position, self._transformedImage) def delete(self): self._screen._delete(self._item) From e90c9eaef8924600bcb25e2238dd199ae26958eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bia=C5=82ow=C4=85s?= Date: Tue, 4 Mar 2025 22:14:07 +0100 Subject: [PATCH 7/8] stamping code improved --- Lib/turtle.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Lib/turtle.py b/Lib/turtle.py index 9bd32bbc81f635..a98529c5ab5e57 100644 --- a/Lib/turtle.py +++ b/Lib/turtle.py @@ -873,9 +873,6 @@ def __init__(self, screen, image): self._transformMatrix = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] self._item = screen._createimage(self._transformedImage) - def clone(self, *args, **kwargs): - return self.__class__(self._screen, self._originalImage, *args, **kwargs) - def _transform_coordinates(self, x, y): m = self._transformMatrix return (m[0][0] * x + m[0][1] * y + m[0][2], @@ -1010,6 +1007,11 @@ def draw(self, position, orientation, tilt): self._currentTilt = tilt self._screen._drawimage(self._item, position, self._transformedImage) + def stamp(self, position, orientation, tilt): + stamp = self.__class__(self._screen, self._originalImage) + stamp.draw(position, orientation, tilt) + return stamp + def delete(self): self._screen._delete(self._item) @@ -3331,8 +3333,7 @@ def stamp(self): screen._drawpoly(item, poly, fill=self._cc(fc), outline=self._cc(oc), width=self._outlinewidth, top=True) elif ttype == "transformable_image": - stitem = self.turtle._item.clone() - stitem.draw(self._position, self._orient, self._tilt) + stitem = self.turtle._item.stamp(self._position, self._orient, self._tilt) self.stampItems.append(stitem) self.undobuffer.push(("stamp", stitem)) return stitem From 02aa5823d42f1668286181a2fadc3757135a7294 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Tue, 4 Mar 2025 21:35:54 +0000 Subject: [PATCH 8/8] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2025-03-04-21-35-52.gh-issue-130715.4k4xhd.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2025-03-04-21-35-52.gh-issue-130715.4k4xhd.rst diff --git a/Misc/NEWS.d/next/Library/2025-03-04-21-35-52.gh-issue-130715.4k4xhd.rst b/Misc/NEWS.d/next/Library/2025-03-04-21-35-52.gh-issue-130715.4k4xhd.rst new file mode 100644 index 00000000000000..5456fe00c426e9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-03-04-21-35-52.gh-issue-130715.4k4xhd.rst @@ -0,0 +1 @@ +Turtle library - added optional rotate_image_shape parameter to addshape() method to allow turtle head rotation when shape is set to an image.