Setting colors on a trimesh union mesh according to the original component meshes - python

I'm working with the trimesh Python library, and I couldn't wrap my head around working with colors from reading the docs (a Spartan API reference) and examples. I'm not even sure if I'm setting the face colors right, if I can modify mesh.visual.face_colors directly or if I should make a ColorVisuals-type object.
But the main question is, can I set the color of different faces of a mesh approximately (I know there can be nasty edge cases) according to it being "overlapping" to faces in the original meshes I had before?
from shapely import Polygon
import trimesh
pts = ((100, 100), (400, 100), (400, 400), (100, 400))
hole = ((150, 150), (350, 150), (350, 350), (150, 350))
p = Polygon(pts, [hole])
mesh = trimesh.creation.extrude_polygon(p, 100)
other = mesh.copy()
other.apply_translation((150, 50, 50))
mesh = mesh.union(other)
# Silly idea (most faces wouldn't be the same) and it doesn't work, I get an error.
# other_v_set = set(other.vertices)
# colors = [([255, 0, 0] if set(mesh.vertices[v] for v in f).issubset(other_v_set)
# else [0, 255, 0]) for f in mesh.faces]
# mesh.visual = trimesh.visual.ColorVisuals(mesh, colors)
mesh.show()

Related

How to make a L-shaped body in pymunk?

I am new to pymunk and I want to make a L-shaped body like this.
I read that it is possible to have two shapes attached to same body but the best I got is this.
The code I used is this:
import pymunk
import pygame
import pymunk.pygame_util
pygame.init()
size = 640, 240
screen = pygame.display.set_mode(size)
draw_options = pymunk.pygame_util.DrawOptions(screen)
space = pymunk.Space()
space.gravity = 0, 90
b0 = space.static_body
segment = pymunk.Segment(b0, (0, 200), (640, 200), 4)
segment.elasticity = 1
body = pymunk.Body(mass=1, moment=10)
body.position = 300, 50
box = pymunk.Poly.create_box(body, (100, 50))
box.elasticity = 0.9
box.friction = 0.8
box2 = pymunk.Poly.create_box(body, (50, 100))
space.add(body, box, box2, segment)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill(color="GRAY")
space.debug_draw(draw_options)
pygame.display.update()
space.step(0.01)
pygame.quit()
Is there any way to do make a L-shaped body?
Thanks in advance!
The problem is that both Poly shapes that are created with the shorthand create_box method are created with their center at the position of the body they are attached to. So the two boxes are positioned on top of each other.
To fix this you need to use the more generic Poly constructor. In this way you can pick the positions as you want.
If I just take your picture and write up the coordinates times 10, it would be something like this:
box = pymunk.Poly(body, [(0, 0), (60, 0), (60, 30), (0, 30)])
box.elasticity = 0.9
box.friction = 0.8
box2 = pymunk.Poly(body, [(0, 30), (30, 30), (30, 60), (0, 60)])
box2.elasticity = 0.9
box2.friction = 0.8
However, note that the coordinates are relative to the body they are attached to, and the center of gravity is where the body is positioned. That means that the resulting L shape will behave as if its a hollow shape with a super heavy stone in one corner.
If this is not what you want to can adjust the coordinates, for example like this (I just subtracted 30 from each one. This can probably be tweaked even more depending on what your goal is):
box = pymunk.Poly(body, [(-30, -30), (30, -30), (30, 0), (-30, 0)])
box.elasticity = 0.9
box.friction = 0.8
box2 = pymunk.Poly(body, [(-30, 0), (0, 0), (0, 30), (-30, 30)])
box2.elasticity = 0.9
box2.friction = 0.8
This is the easiest to understand way (at least I think so), but there are two other options available. You can instead give a translate pymunk.Transform as an option to the Poly constructor, with the downside that it is less obvious what the result will be. Or you can move the center of gravity on the body with the center_of_gravity property, with the downside that when you look at the position of the body it will still be at the "corner" of the shape.
Try this
https://pymunk-tutorial.readthedocs.io/en/latest/shape/shape.html#l-shaped-segment
body = pymunk.Body(mass=1, moment=1000)
body.position = (100, 200)
s1 = pymunk.Segment(body, (0, 0), (50, 0), 8)
s1.density = 1
s1.elasticity = 0.999
space.add(body, s1)

Get color of coordinate of figure drawn with Python Zelle graphics

In python, How can I get the color of a specific coordinate of any figure, that I've drawn with the Zelle graphics module?
I'm using Python with the Zelle graphics module to process with my circles and lines. I'm trying to get a color of aspecific coordinate (or pixel?) on the canvas I'm drawing on. What method or other modules do I have to use in order to achieve this?
I thought the getPixel() method would work, but it does not since it's for image processing, not for drawn pictures. My current code:
from math import *
from time import *
from graphics import *
def main():
paper = GraphWin('shjaji20', 300, 300)
paper.setBackground('white')
road0 = Circle(Point(150, 150), 100)
road1 = Line(Point(150, 50), Point(150, 0))
road2 = Line(Point(50, 150), Point(0, 150))
road3 = Line(Point(250, 150), Point(300, 150))
road4 = Line(Point(150, 250), Point(150, 300))
road0.draw(paper)
road1.draw(paper)
road2.draw(paper)
road3.draw(paper)
road4.draw(paper)
car = Circle(Point(0, 150), 5)
car.setFill('white')
car.draw(paper)
for i in range(1000):
car.move(1, 0)
time.sleep(.05)
print car.getPixel(150, 0) ***#I tried many ways but don't work! Here's the problem***
main()
This can be done, in a way. Zelle's graphics.py is built atop Python's tkinter library which can both identify which graphic object sits above a given point as well as interrogate that object's color. The key is knowing that a GraphWin instance is also a tkinter Canvas by inheritance:
from time import sleep
from graphics import *
paper = GraphWin(width=300, height=300)
road = Circle(Point(150, 150), 100)
road.setFill('pink')
road.draw(paper)
Line(Point(150, 50), Point(150, 0)).draw(paper)
Line(Point(50, 150), Point(0, 150)).draw(paper)
Line(Point(250, 150), Point(300, 150)).draw(paper)
Line(Point(150, 250), Point(150, 300)).draw(paper)
car = Circle(Point(0, 150), 5)
car.setFill('white')
car.draw(paper)
for _ in range(300):
car.move(1, 0)
center = car.getCenter()
overlapping = paper.find_overlapping(center.x, center.y, center.x, center.y)
if overlapping:
print(paper.itemcget(overlapping[0], "fill"))
sleep(0.05)
As the small circle moves across the lines, "black" will be printed to the console. As it moves across the central circle, "pink" we be printed. The code is for Python3, if you're using Python2 you'll need to adjust.

Fix PIL.ImageDraw.Draw.line with wide lines

From the PIL Documentation:
PIL.ImageDraw.Draw.line(xy, fill=None, width=0)
Draws a line between the coordinates in the xy list.
Parameters:
xy – Sequence of either 2-tuples like [(x, y), (x, y), ...] or numeric values like [x, y, x, y, ...].
fill – Color to use for the line.
width – The line width, in pixels. Note that line joins are not handled well, so wide polylines will not look good.
I'm looking for a fix for this issue. A good solution for me would be to have the line drawn by PIL.ImageDraw have rounded ends (capstyle in TKinter). Is there an equivalent in PIL.ImageDraw?
This is what I would like to obtain:
Minimal Working Example:
from PIL import Image, ImageDraw
WHITE = (255, 255, 255)
BLUE = "#0000ff"
MyImage = Image.new('RGB', (600, 400), WHITE)
MyDraw = ImageDraw.Draw(MyImage)
MyDraw.line([100,100,150,200], width=40, fill=BLUE)
MyDraw.line([150,200,300,100], width=40, fill=BLUE)
MyDraw.line([300,100,500,300], width=40, fill=BLUE)
MyImage.show()
Result from MWE:
There are standard option joint='curve' of the ImageDraw.line designed to fix it.
Your example may look like
from PIL import Image, ImageDraw
WHITE = (255, 255, 255)
BLUE = "#0000ff"
MyImage = Image.new('RGB', (600, 400), WHITE)
MyDraw = ImageDraw.Draw(MyImage)
line_points = [(100, 100), (150, 200), (300, 100), (500, 300)]
MyDraw.line(line_points, width=40, fill=BLUE, joint='curve')
MyImage.show()
Special care is required to address the end-points, but joints are fixed.
The result:
I have the same problem as you. However, you can easily solve the problem by simply plotting a circle of the same diameter as the line widths at each vertex. Below is your code, slightly modified, to fix the problem
from PIL import Image, ImageDraw
WHITE = (255, 255, 255)
BLUE = "#0000ff"
RED = "#ff0000"
MyImage = Image.new('RGB', (600, 400), WHITE)
MyDraw = ImageDraw.Draw(MyImage)
# Note: Odd line widths work better for this algorithm,
# even though the effect might not be noticeable at larger line widths
LineWidth = 41
MyDraw.line([100,100,150,200], width=LineWidth, fill=BLUE)
MyDraw.line([150,200,300,100], width=LineWidth, fill=BLUE)
MyDraw.line([300,100,500,300], width=LineWidth, fill=BLUE)
Offset = (LineWidth-1)/2
# I have plotted the connecting circles in red, to show them better
# Even though they look smaller than they should be, they are not.
# Look at the diameter of the circle and the diameter of the lines -
# they are the same!
MyDraw.ellipse ((150-Offset,200-Offset,150+Offset,200+Offset), fill=RED)
MyDraw.ellipse ((300-Offset,100-Offset,300+Offset,100+Offset), fill=RED)
MyImage.show()

Python PIL Editing Pixels versus ImageDraw.point

I am working on an image-generation program, and I have an issue trying to directly edit the pixels of an image.
My original method, which works, was simply:
image = Image.new('RGBA', (width, height), background)
drawing_image = ImageDraw.Draw(image)
# in some loop that determines what to draw and at what color
drawing_image.point((x, y), color)
This works fine, but I thought directly editing pixels might be slightly faster. I plan on using "very" high resolutions (maybe 10000px by 10000px), so even a slight decrease in time per pixel will be a large decrease overall.
I tried using this:
image = Image.new('RGBA', (width, height), background)
pixels = image.load()
# in some loop that determines what to draw and at what color
pixels[x][y] = color # note: color is a hex-formatted string, i.e "#00FF00"
This gives me an error:
Traceback (most recent call last):
File "my_path\my_file.py", line 100, in <module>
main()
File "my_path\my_file.py", line 83, in main
pixels[x][y] = color
TypeError: argument must be sequence of length 2
How does the actual pixels[x][y] work? I seem to be missing a fundamental concept here (I've never worked with directly editing pixels prior to this), or at least just not understanding what arguments are required. I even tried pixels[x][y] = (0, 0, 0), but that raised the same error.
In addition, is there a faster way to edit the pixels? I've heard that using the pixels[x][y] = some_color is faster than drawing to the image, but I'm open to any other faster method.
Thanks in advance!
You need to pass a tuple index as pixels[(x, y)] or simply pixels[x, y], for example:
#-*- coding: utf-8 -*-
#!python
from PIL import Image
width = 4
height = 4
background = (0, 0, 0, 255)
image = Image.new("RGBA", (width, height), background)
pixels = image.load()
pixels[0, 0] = (255, 0, 0, 255)
pixels[0, 3] = (0, 255, 0, 255)
pixels[3, 3] = (0, 0, 255, 255)
pixels[3, 0] = (255, 255, 255, 255)
image.save("image.png")

How do I draw text at an angle using python's PIL?

Using Python I want to be able to draw text at different angles using PIL.
For example, imagine you were drawing the number around the face of a clock. The number 3 would appear as expected whereas 12 would we drawn rotated counter-clockwise 90 degrees.
Therefore, I need to be able to draw many different strings at many different angles.
Draw text into a temporary blank image, rotate that, then paste that onto the original image. You could wrap up the steps in a function. Good luck figuring out the exact coordinates to use - my cold-fogged brain isn't up to it right now.
This demo writes yellow text on a slant over an image:
# Demo to add rotated text to an image using PIL
import Image
import ImageFont, ImageDraw, ImageOps
im=Image.open("stormy100.jpg")
f = ImageFont.load_default()
txt=Image.new('L', (500,50))
d = ImageDraw.Draw(txt)
d.text( (0, 0), "Someplace Near Boulder", font=f, fill=255)
w=txt.rotate(17.5, expand=1)
im.paste( ImageOps.colorize(w, (0,0,0), (255,255,84)), (242,60), w)
It's also usefull to know our text's size in pixels before we create an Image object. I used such code when drawing graphs. Then I got no problems e.g. with alignment of data labels (the image is exactly as big as the text).
(...)
img_main = Image.new("RGB", (200, 200))
font = ImageFont.load_default()
# Text to be rotated...
rotate_text = u'This text should be rotated.'
# Image for text to be rotated
img_txt = Image.new('L', font.getsize(rotate_text))
draw_txt = ImageDraw.Draw(img_txt)
draw_txt.text((0,0), rotate_text, font=font, fill=255)
t = img_value_axis.rotate(90, expand=1)
The rest of joining the two images together is already described on this page.
When you rotate by an "unregular" angle, you have to improve this code a little bit. It actually works for 90, 180, 270...
Here is a working version, inspired by the answer, but it works without opening or saving images.
The two images have colored background and alpha channel different from zero to show what's going on. Changing the two alpha channels from 92 to 0 will make them completely transparent.
from PIL import Image, ImageFont, ImageDraw
text = 'TEST'
font = ImageFont.truetype(r'C:\Windows\Fonts\Arial.ttf', 50)
width, height = font.getsize(text)
image1 = Image.new('RGBA', (200, 150), (0, 128, 0, 92))
draw1 = ImageDraw.Draw(image1)
draw1.text((0, 0), text=text, font=font, fill=(255, 128, 0))
image2 = Image.new('RGBA', (width, height), (0, 0, 128, 92))
draw2 = ImageDraw.Draw(image2)
draw2.text((0, 0), text=text, font=font, fill=(0, 255, 128))
image2 = image2.rotate(30, expand=1)
px, py = 10, 10
sx, sy = image2.size
image1.paste(image2, (px, py, px + sx, py + sy), image2)
image1.show()
The previous answers draw into a new image, rotate it, and draw it back into the source image. This leaves text artifacts. We don't want that.
Here is a version that instead crops the area of the source image that will be drawn onto, rotates it, draws into that, and rotates it back. This means that we draw onto the final surface immediately, without having to resort to masks.
def draw_text_90_into (text: str, into, at):
# Measure the text area
font = ImageFont.truetype (r'C:\Windows\Fonts\Arial.ttf', 16)
wi, hi = font.getsize (text)
# Copy the relevant area from the source image
img = into.crop ((at[0], at[1], at[0] + hi, at[1] + wi))
# Rotate it backwards
img = img.rotate (270, expand = 1)
# Print into the rotated area
d = ImageDraw.Draw (img)
d.text ((0, 0), text, font = font, fill = (0, 0, 0))
# Rotate it forward again
img = img.rotate (90, expand = 1)
# Insert it back into the source image
# Note that we don't need a mask
into.paste (img, at)
Supporting other angles, colors etc is trivial to add.
Here's a fuller example of watermarking diagonally. Handles arbitrary image ratios, sizes and text lengths by calculating the angle of the diagonal and font size.
from PIL import Image, ImageFont, ImageDraw
import math
# sample dimensions
pdf_width = 1000
pdf_height = 1500
#text_to_be_rotated = 'Harry Moreno'
text_to_be_rotated = 'Harry Moreno (morenoh149#gmail.com)'
message_length = len(text_to_be_rotated)
# load font (tweak ratio based on your particular font)
FONT_RATIO = 1.5
DIAGONAL_PERCENTAGE = .5
diagonal_length = int(math.sqrt((pdf_width**2) + (pdf_height**2)))
diagonal_to_use = diagonal_length * DIAGONAL_PERCENTAGE
font_size = int(diagonal_to_use / (message_length / FONT_RATIO))
font = ImageFont.truetype(r'./venv/lib/python3.7/site-packages/reportlab/fonts/Vera.ttf', font_size)
#font = ImageFont.load_default() # fallback
# target
image = Image.new('RGBA', (pdf_width, pdf_height), (0, 128, 0, 92))
# watermark
opacity = int(256 * .5)
mark_width, mark_height = font.getsize(text_to_be_rotated)
watermark = Image.new('RGBA', (mark_width, mark_height), (0, 0, 0, 0))
draw = ImageDraw.Draw(watermark)
draw.text((0, 0), text=text_to_be_rotated, font=font, fill=(0, 0, 0, opacity))
angle = math.degrees(math.atan(pdf_height/pdf_width))
watermark = watermark.rotate(angle, expand=1)
# merge
wx, wy = watermark.size
px = int((pdf_width - wx)/2)
py = int((pdf_height - wy)/2)
image.paste(watermark, (px, py, px + wx, py + wy), watermark)
image.show()
Here it is in a colab https://colab.research.google.com/drive/1ERl7PiX6xKy5H9EEMulBKPgglF6euCNA?usp=sharing you should provide an example image to the colab.
I'm not saying this is going to be easy, or that this solution will necessarily be perfect for you, but look at the documentation here:
http://effbot.org/imagingbook/pil-index.htm
and especially pay attention to the Image, ImageDraw, and ImageFont modules.
Here's an example to help you out:
import Image
im = Image.new("RGB", (100, 100))
import ImageDraw
draw = ImageDraw.Draw(im)
draw.text((50, 50), "hey")
im.rotate(45).show()
To do what you really want you may need to make a bunch of separate correctly rotated text images and then compose them all together with some more fancy manipulation. And after all that it still may not look great. I'm not sure how antialiasing and such is handled for instance, but it might not be good. Good luck, and if anyone has an easier way, I'd be interested to know as well.
If you a using aggdraw, you can use settransform() to rotate the text. It's a bit undocumented, since effbot.org is offline.
# Matrix operations
def translate(x, y):
return np.array([[1, 0, x], [0, 1, y], [0, 0, 1]])
def rotate(angle):
c, s = np.cos(angle), np.sin(angle)
return np.array([[c, -s, 0], [s, c, 0], [0, 0, 1]])
def draw_text(image, text, font, x, y, angle):
"""Draw text at x,y and rotated angle radians on the given PIL image"""
m = np.matmul(translate(x, y), rotate(angle))
transform = [m[0][0], m[0][1], m[0][2], m[1][0], m[1][1], m[1][2]]
draw = aggdraw.Draw(image)
draw.settransform(transform)
draw.text((tx, ty), text, font)
draw.settransform()
draw.flush()

Categories