I have used the Python Imaging Library to load a .ttf font. Here is the code:
self.rect = Image.new("RGBA", (600,100), (255,255,255))
self.draw = ImageDraw.Draw(self.rect)
self.font = ImageFont.truetype("font.ttf", 96)
self.draw.text((5,0), "activatedgeek", (0,0,0), font=self.font)
self.texture = self.loadFont(self.rect)
Here is the loadFont() function of the respective class:
def loadFont(self, im):
try:
ix, iy, image = im.size[0], im.size[1], im.tostring("raw", "RGBA", 0, -1)
except SystemError:
ix, iy, image = im.size[0], im.size[1], im.tostring("raw", "RGBX", 0, -1)
retid = gl.glGenTextures(1)
gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT,1)
gl.glBindTexture(gl.GL_TEXTURE_2D,retid)
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR_MIPMAP_LINEAR)
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR_MIPMAP_LINEAR)
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP)
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP)
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_GENERATE_MIPMAP, gl.GL_TRUE)
gl.glTexImage2D(gl.GL_TEXTURE_2D,0,3,ix,iy,0,gl.GL_RGBA,gl.GL_UNSIGNED_BYTE,image)
return retid
Here is a snapshot I have taken using glReadPixels() unfortunately same as one rendered on the window created using PyQt.
It shows an unwanted border, some artefact. Please help me rectify this.
Have you considered using a more reasonable wrap state, such as GL_CLAMP_TO_EDGE? I have a strong feeling that this is related to border color beyond the edges of your texture image.
There are a number of approaches you could take to solve an issue like this, ranging from pre-multiplied alpha to an extra texel border around the entire image, but the simplest thing to try would be GL_CLAMP_TO_EDGE.
GL_CLAMP is something of a joke as far as wrap modes go, it does not clamp the range of texture coordinates to texel centers and calamity ensues when the nearest texel becomes the border color. Needless to say, this behavior is usually undesirable.
Related
I'm trying to do an image outline in PIL. My goal is to have text with some extra lines that will be visible on any background (including images, not just solid colours), kinda like outlined movie subtitles.
There are many answers here and texts on the web, but none of them work for me. They boil down to:
(usually for text) Do for-loops to put text to positions (x+dx, y+dy) for dx, dx in range(-radius, radius + 1).
Blur the image.
Make a contour and draw it with a thick line.
Edge-detect algorithms.
I tried these, but the quality of the results was bad. This is for a project that really needs to look professional.
If I was doing this in Gimp, I would likely select the transparent part of the image with "Select by Color" tool, then invert the selection (to select everything that's not transparent), then grow the selection (giving me nicely rounded shape around corners), then feather it a bit (to get smoother lines), and then paint it with a solid colour, on a layer below my image.
Is it possible to do something like this in PIL, or anything compatible (basically, anything that can take NumPy array "images")?
Unfortunately, you haven't shown any of your trials, so that one could've seen, what your results look like to get an impression, what you consider "bad". So, as you mention images stored as NumPy arrays, OpenCV might be an option here.
I will follow a combination of the mentioned ideas:
Generate an empty text plane with the same dimensions as the image, and add an additional alpha channel set to 0 (not visible).
Put the text outline: Desired background color (let's say yellow), large thickness.
Blur the whole text plane heavily, including the alpha channel. So, you get your feathered outline.
Put the actual text: Desired foreground color (let's say black), normal thickness.
Blur the whole text plane slightly, just to smooth the generated text. (Beautiful text is not one of OpenCV's strengths!)
Generate output by linear combination of image and text plane using the plane's alpha channel.
That'd be the code:
import cv2
import numpy as np
# Open image, Attention: OpenCV uses BGR ordering by default!
image = cv2.imread('path/your/image.png', cv2.IMREAD_COLOR)
# Set up text properties
loc = (250, 500)
text = 'You were the chosen one!'
c_fg = (0, 255, 255, 255)
c_bg = (0, 0, 0, 255)
# Initialize overlay text plane
overlay = np.zeros((image.shape[0], image.shape[1], 4), np.uint8)
# Put text outline, larger thickness, color of outline (here: black)
cv2.putText(overlay, text, loc, cv2.FONT_HERSHEY_COMPLEX, 1.0, c_bg, 9, cv2.LINE_AA)
# Blur text plane (including alpha channel): Heavy blur
overlay = cv2.GaussianBlur(overlay, (21, 21), sigmaX=10, sigmaY=10)
# Put text, normal thickness, color of overlay (here: yellow)
cv2.putText(overlay, text, loc, cv2.FONT_HERSHEY_COMPLEX, 1.0, c_fg, 2, cv2.LINE_AA)
# Blur text plane (inclusing alpha channel): Very slight blur
overlay = cv2.GaussianBlur(overlay, (3, 3), sigmaX=0.5, sigmaY=0.5)
# Add overlay text plane to image (channel by channel)
output = np.zeros(image.shape, np.uint8)
for i in np.arange(3):
output[:, :, i] = image[:, :, i] * ((255 - overlay[:, :, 3]) / 255) + overlay[:, :, i] * (overlay[:, :, 3] / 255)
cv2.imshow('output', output)
cv2.waitKey(0)
cv2.destroyAllWindows()
The parameters of the blurring are manually set. Different images and text sizes will require further adaptations.
Here's an example output:
Even using a foreground color similar to the image' background, the text is still readable - at least from my point of view:
So, now the big question: Is that result considered "bad"?
Hope that helps!
I am creating a program which must change the color of individual pixels in a pyglet window. I am unable to find any way to do this in the docs. Is there a way to do this?
For funsies, I'll add another answer that is more along the lines of what you might need. Because the window itself will be whatever "clear" color buffer you decide via:
window = pyglet.window.Window(width=width, height=height)
pyglet.gl.glClearColor(0.5,0,0,1) # Note that these are values 0.0 - 1.0 and not (0-255).
So changing the background is virtually impossible because it's "nothing".
You can however draw pixels on the background via the .draw() function.
import pyglet
from random import randint
width, height = 500, 500
window = pyglet.window.Window(width=width, height=height)
#window.event
def on_draw():
window.clear()
for i in range(10):
x = randint(0,width)
y = randint(0,height)
pyglet.graphics.draw(1, pyglet.gl.GL_POINTS,
('v2i', (x, y)),
('c3B', (255, 255, 255))
)
pyglet.app.run()
This will create 10 randomly placed white dots on the background.
To add anything above that simply place your .blit() or .draw() features after the pyglet.graphics.draw() line.
You could use the magic function SolidColorImagePattern and modify the data you need.
R,G,B,A = 255,255,255,255
pyglet.image.SolidColorImagePattern((R,G,B,A).create_image(width,height)
This is a .blit():able image. It's white, and probably not what you want.
So we'll do some more wizardry and swap out all the pixels for random ones (War of the ants):
import pyglet
from random import randint
width, height = 500, 500
window = pyglet.window.Window(width=width, height=height)
image = pyglet.image.SolidColorImagePattern((255,255,255,255)).create_image(width, height)
data = image.get_image_data().get_data('RGB', width*3)
new_image = b''
for i in range(0, len(data), 3):
pixel = bytes([randint(0,255)]) + bytes([randint(0,255)]) + bytes([randint(0,255)])
new_image += pixel
image.set_data('RGB', width*3, new_image)
#window.event
def on_draw():
window.clear()
image.blit(0, 0)
pyglet.app.run()
For educational purposes, I'll break it down into easier chunks.
image = pyglet.image.SolidColorImagePattern((255,255,255,255)).create_image(width, height)
Creates a solid white image, as mentioned. It's width and height matches the window-size.
We then grab the image data:
data = image.get_image_data().get_data('RGB', width*3)
This bytes string will contain width*height*<format>, meaning a 20x20 image will be 1200 bytes big because RGB takes up 3 bytes per pixel.
new_image = b''
for i in range(0, len(data), 3):
pixel = bytes([randint(0,255)]) + bytes([randint(0,255)]) + bytes([randint(0,255)])
new_image += pixel
This whole block loops over all the pixels (len(data) is just a convenience thing, you could do range(0, width*height*3, 3) as well, but meh.
The pixel contists of 3 randint(255) bytes objects combined into one string like so:
pixel = b'xffxffxff'
That's also the reason for why we step 3 in our range(0, len(data), 3). Because one pixel is 3 bytes "wide".
Once we've generated all the pixels (for some reason the bytes object image can't be modified.. I could swear I've modified bytes "strings" before.. I'm tired tho so that's probably a utopian dream or something.
Anyhow, once all that sweet image building is done, we give the image object it's new data by doing:
image.set_data('RGB', width*3, new_image)
And that's it. Easy as butter in sunshine on a -45 degree winter day.
Docs:
https://pyglet.readthedocs.io/en/pyglet-1.2-maintenance/programming_guide/quickstart.html
https://github.com/Torxed/PygletGui/blob/master/gui_classes_generic.py
https://pythonhosted.org/pyglet/api/pyglet.image.ImageData-class.html#get_image_data
https://pythonhosted.org/pyglet/api/pyglet.image.ImageData-class.html#set_data
You can also opt in to get a region, and just modify a region.. But I'll leave the tinkering up to you :)
You can blit pixels into background 'image'. You can look at this Stack Overflow question.
If you mean background color, I can help. There is one option that I know of, the pyglet.gl.glClearColor function.
for example,:
import pyglet
from pyglet.gl import glClearColor
win = pyglet.window.Window(600, 600, caption = "test")
glClearColor(255, 255, 255, 1.0) # red, green, blue, and alpha(transparency)
def on_draw():
win.clear()
That will create a window with a white background(as opposed to the default, black)
I googled, checked the documentation of PIL library and much more, but I couldn't find the answer to my simple question: how can I fill an existing image with a desired color?
(I am using from PIL import Image and from PIL import ImageDraw)
This command creates a new image filled with a desired color
image = Image.new("RGB", (self.width, self.height), (200, 200, 200))
But I would like to reuse the same image without the need of calling "new" every time
Have you tried:
image.paste(color, box)
where box can be a 2-tuple giving the upper left corner, a 4-tuple defining the left, upper, right, and lower pixel coordinate, or None (same as (0, 0))
Since you want to fill the entire image, you can use the following:
image.paste( (200,200,200), [0,0,image.size[0],image.size[1]])
One possibility is to draw a rectangle:
from PIL import Image
from PIL import ImageDraw
#...
draw = ImageDraw.Draw(image)
draw.rectangle([(0,0),image.size], fill = (200,200,200) )
Or (untested):
draw = ImageDraw.Draw(image).rectangle([(0,0),image.size], fill = (200,200,200) )
(Although it is surprising there is no simpler method to fill a whole image with one background color, like setTo for opencv)
I am trying to input an image (image1) and flip it horizontally and then save to a file (image2). This works but not the way I want it to
currently this code gives me a flipped image but it just shows the bottom right quarter of the image, so it is the wrong size. Am I overwriting something somewhere? I just want the code to flip the image horizontally and show the whole picture flipped. Where did I go wrong?
and I cannot just use a mirror function or reverse function, I need to write an algorithm
I get the correct window size but the incorrect image size
def Flip(image1, image2):
img = graphics.Image(graphics.Point(0, 0), image1)
X, Y = img.getWidth(), img.getHeight()
for y in range(Y):
for x in range(X):
r, g, b = img.getPixel(x,y)
color = graphics.color_rgb(r, g, b)
img.setPixel(X-x, y, color)
win = graphics.GraphWin(img, img.getWidth(), img.getHeight())
img.draw(win)
img.save(image2)
I think your problem is in this line:
win = graphics.GraphWin(img, img.getWidth(), img.getHeight())
The first argument to the GraphWin constructor is supposed to be the title, but you are instead giving it an Image object. It makes me believe that maybe the width and height you are supplying are then being ignored. The default width and height for GraphWin is 200 x 200, so depending on the size of your image, that may be why only part of it is being drawn.
Try something like this:
win = graphics.GraphWin("Flipping an Image", img.getWidth(), img.getHeight())
Another problem is that your anchor point for the image is wrong. According to the docs, the anchor point is where the center of the image will be rendered (thus at 0,0 you are only seeing the bottom right quadrant of the picture). Here is a possible solution if you don't know what the size of the image is at the time of creation:
img = graphics.Image(graphics.Point(0, 0), image1)
img.move(img.getWidth() / 2, img.getHeight() / 2)
You are editing your source image. It would be
better to create an image copy and set those pixels instead:
create a new image for editing:
img_new = img
Assign the pixel values to that:
img_new.setPixel(X-x, y, color)
And draw that instead:
win = graphics.GraphWin(img_new, img_new.getWidth(), img_new.getHeight())
img_new.draw(win)
img_new.save(image2)
This will also check that your ranges are correct. if they are not, you will see both flipped and unflipped portions in the final image, showing which portions are outside of your ranges.
If you're not opposed to using an external library, I'd recommend the Python Imaging Library. In particular, the ImageOps module has a mirror function that should do exactly what you want.
Can someone give me some example code that creates a surface with a transparent background in pygame?
This should do it:
image = pygame.Surface([640,480], pygame.SRCALPHA, 32)
image = image.convert_alpha()
Make sure that the color depth (32) stays explicitly set else this will not work.
You can also give it a colorkey, much like GIF file transparency. This is the most common way to make sprites. The original bitmap has the artwork, and has a certain color as background that will not be drawn, which is the colorkey:
surf.set_colorkey((255,0,255)) // Sets the colorkey to that hideous purple
Surfaces that uses colorkey instead of alpha are a lot faster to blit since they don't require any blend math. The SDL surface uses a simple bitmask when it has a colorkey set, which blits practically without overhead.
You have 3 possibilities:
Set a transparent color key with set_colorkey()
The color key specifies the color that is treated as transparent. For example, if you have an image with a black background that should be transparent, set a black color key:
my_surface.set_colorkey((0, 0, 0))
You can enable additional functions when creating a new surface. Set the SRCALPHA flag to create a surface with an image format that includes a per-pixel alpha. The initial value of the pixels is (0, 0, 0, 0):
my_surface = pygame.Surface((width, height), pygame.SRCALPHA)
Use convert_alpha() to create a copy of the Surface with an image format that provides alpha per pixel.
However, if you create a new surface and use convert_alpha(), the alpha channels are initially set to maximum. The initial value of the pixels is (0, 0, 0, 255). You need to fill the entire surface with a transparent color before you can draw anything on it:
my_surface = pygame.Surface((width, height))
my_surface = my_surface.convert_alpha()
my_surface.fill((0, 0, 0, 0))