Why glDrawpixels does not render my transparent background? - python

I want to render font in opengl with transparent background.
The main problem is that my background is always opaque black !
here is my code :
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
font = ImageFont.truetype("/usr/share/fonts/truetype/wqy/wqy-microhei.ttc", 16)
textsize = font.getsize(u"当前运行列表")
im = Image.new("RGB",textsize,"#000")
draw = ImageDraw.Draw(im)
transparent_area = (0,0,im.size[0],im.size[1])
mask=Image.new('L', im.size, color=0)
drawmask = ImageDraw.Draw(mask)
drawmask.rectangle(transparent_area, fill=0)
im.putalpha(mask)
draw.text((0,0), u"当前运行列表", font=font, fill="red")
#im.save('render.png')
ix, iy, image = im.size[0], im.size[1], im.tostring("raw", "RGBA", 0, -1)
glDrawPixels(ix,iy,GL_RGBA,GL_UNSIGNED_BYTE,image)

Cheers, it is not fully clear what are you trying to acheive. You could either be trying to have a transparent font rendered over some other OpenGL geometry, or maybe you want to render contents of the window using OpenGL and make parts of the window transparent (so you can see windows behind it).
To make window transparent, you need to use:
// Set WS_EX_LAYERED on this window
SetWindowLong(hwnd,
GWL_EXSTYLE,
GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED);
// Make this window transparent
int transparentColor = RGB(x, y, z);
SetLayeredWindowAttributes(hwnd, transparentColor, 255, LWA_COLORKEY);
This enables so called color keying on the window, that means anything that is rendered using RGB(x, y, z) gets transparent, and the window is not visible in these parts. Note that this transparency is binary, it is either fully transparent or fully opaque. Also, you need to be using GDI to render to your window. That means either do not use OpenGL at all, and use function like BitBlt() or StretchBlt() instead, or use OpenGL to render into a P-buffer (or into a FBO, framebuffer object), then use glReadPixels() to copy the output, and then use BitBlt() again to display the results using GDI. In your case where you are trying to render a piece of text, you might be better off without using OpenGL.
So, in your example you could make black color transparent, i.e. x = y = z = 0, do not use OpenGL, and create a device independent bitmap (DIB) instead using CreateDIBSection(), fill it with image instead of passing it to glDrawPixels() and render to window using BitBlt().

added this at beginning
glPixelStorei( GL_UNPACK_SWAP_BYTES, GL_FALSE )
glPixelStorei( GL_UNPACK_LSB_FIRST, GL_FALSE )
glPixelStorei( GL_UNPACK_ROW_LENGTH, 0 )
glPixelStorei( GL_UNPACK_SKIP_ROWS, 0 )
glPixelStorei( GL_UNPACK_SKIP_PIXELS, 0 )
glPixelStorei( GL_UNPACK_ALIGNMENT, 1 )

Related

Add element to OLED display via PIL/Python without erasing rest

I have a SH1106 display connected to my Raspberry Pi that I'm controlling using luma.oled.
I can display all kind of content in different fonts, which is great. However, I can't figure out how to add something to what's currently being displayed without refreshing the whole display. My code is like this:
from os import system
import serial
from time import sleep
from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import sh1106
from PIL import ImageFont
# config display
device = sh1106(i2c(port=1, address=0x3C), rotate=0)
device.clear()
FA_solid = ImageFont.truetype('/home/pi/Desktop/tests/fa-solid-900.ttf', 16)
FA_regular = ImageFont.truetype('/home/pi/Desktop/tests/fa-regular-400.ttf', 16)
text_large = ImageFont.truetype('/home/pi/Desktop/tests/coolvetica condensed rg.ttf', 48)
text_small = ImageFont.truetype('/home/pi/Desktop/tests/coolvetica condensed rg.ttf', 16)
# display things
def show_icon(code):
with canvas(device) as draw:
draw.text((112, 0), text=code, font=FA_solid, fill="white")
def large_text(content, paddingleft =0, paddingtop =0):
with canvas(device) as draw:
draw.text((0, 0), text=content, font=text_large, fill="white")
def small_text(content, paddingleft =0, paddingtop =0):
with canvas(device) as draw:
draw.text((0, 0), text=content, font=text_small, fill="white")
show_icon("\uf124")
sleep(2)
large_text("Hi ;)")
sleep(10)
device.clear()
This display an icon from fontawesome in the upper right corner, then clears the screen and displays Hi. How can I change this to display the icon + hi? Ideally I'd have "zones" on the screen where I can change the icon zone while keeping the text displayed and vice versa. Thanks!
EDIT --------------------
Here's my code, adapted from Mark's answer below. Better but still not there yet. The Zones 1 and 3 stay the same while 2 is updated but when I redraw the screen, it is blank for half a second and then updates, which I don't want.
def UpdateDisplay(z1,z2,z3):
"""Pass in the three zones and they will be sent to the screen"""
device = sh1106(i2c(port=1, address=0x3C), rotate=0)
# Make a black canvas the size of the entire screen
whole = Image.new("1", (128,64))
# Now paste in the 3 zones to form the whole
whole.paste(z1, (2,2)) # zone1 at top-left
whole.paste(z2, (66,2)) # zone2 at top-right
whole.paste(z3, (2,34)) # zone3 across the bottom
# I save the image here, but you would write it to the screen with "device.display()"
device.display(whole)
return
# Make zone1 dark grey and annotate it
z1 = Image.new("1", (60,30))
z1draw = ImageDraw.Draw(z1)
z1draw.text((10,10),"Zone1", fill="white")
# Make zone2 mid-grey and annotate it
z2 = Image.new("1", (60,30))
z2draw = ImageDraw.Draw(z2)
z2draw.text((10,10),"Zone2", fill="white")
# Make zone3 light grey and annotate it
z3 = Image.new("1", (124,28))
z3draw = ImageDraw.Draw(z3)
z3draw.text((10,10),"Zone3", fill="white")
# Blit all zones to display
UpdateDisplay(z1,z2,z3)
sleep(5)
# Make zone2 mid-grey and annotate it
z2 = Image.new("1", (60,30))
z2draw = ImageDraw.Draw(z2)
z2draw.text((10,10),"Zone2 changed", fill="white")
UpdateDisplay(z1,z2,z3)
I don't have an SH1106 to test with and I have never used the luma library, so there may be a much simpler way of doing what you want. If so, maybe someone will kindly ping me and I'll delete this answer.
I have used PIL quite a lot, so I looked in here around line 28:
background = Image.new("RGB", device.size, "white")
background.paste(frame.resize(size, resample=Image.LANCZOS), posn)
device.display(background.convert(device.mode))
So, it seems you can create a PIL Image and send it to the display like that. The first line creates a blank white canvas the same size as the entire display, the second line pastes another PIL Image onto the canvas at the specified position and the last line sends the image to the display. So, all you need to do, is define your N "zones" separately and draw in them separately (each being a PIL Image), then when you want to update the display, paste your N zones in at the positions you want them and send the completed picture to the display.
Sorry I can't be more precise, but I have nothing to test with. Here's a little example with 3 zones that can be drawn individually and then assembled to a whole before calling device.display()
#!/usr/bin/env python3
from PIL import Image, ImageDraw
def UpdateDisplay(z1,z2,z3):
"""Pass in the three zones and they will be sent to the screen"""
# Make a black canvas the size of the entire screen
whole = Image.new("RGB", (128,64), (0,0,0))
# Now paste in the 3 zones to form the whole
whole.paste(z1, (2,2)) # zone1 at top-left
whole.paste(z2, (66,2)) # zone2 at top-right
whole.paste(z3, (2,34)) # zone3 across the bottom
# I save the image here, but you would write it to the screen with "device.display()"
whole.save('result.png')
return
# Make zone1 dark grey and annotate it
z1 = Image.new("RGB", (60,30), (64,64,64))
z1draw = ImageDraw.Draw(z1)
z1draw.text((10,10),"Zone1")
# Make zone2 mid-grey and annotate it
z2 = Image.new("RGB", (60,30), (128,128,128))
z2draw = ImageDraw.Draw(z2)
z2draw.text((10,10),"Zone2")
# Make zone3 light grey and annotate it
z3 = Image.new("RGB", (124,28), (192,192,192))
z3draw = ImageDraw.Draw(z3)
z3draw.text((10,10),"Zone3")
# Blit all zones to display
UpdateDisplay(z1,z2,z3)
# Now change just zone 2 and update display
z2.paste("red", (0,0,z2.width,z2.height))
UpdateDisplay(z1,z2,z3)
Here is the original display:
And here again after updating just zone2:
ok so I mostly figured it out:
from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import sh1106
from PIL import ImageFont, Image, ImageDraw
### setting up display using LUMA oled
device = sh1106(i2c(port=1, address=0x3C), rotate=0)
device.clear()
### Initialize drawing zone (aka entire screen)
output = Image.new("1", (128,64))
add_to_image = ImageDraw.Draw(output)
### I have the exterior temp and altitude I want to display. Each has an assigned zone for the icon (FontAwesome) and the data
# temp_ext
temp_zone = [(14,44), (36,64)]
temp_start = (14,44)
temp_icon_zone = [(0,48), (15,64)]
temp_icon_start = (3,48)
add_to_image.text(temp_icon_start, "\uf2c9", font=FA_solid, fill="white")
### every time I have a new reading, I basically draw a black rectangle over what I had and the rewrite the text
add_to_image.rectangle(temp_zone, fill="black", outline = "black")
add_to_image.text(temp_start, str(temp_c), font=text_medium, fill="white")
device.display(output)
This enables me to only update the part of the screen I want, leaving the rest as is and, crucially, not having a blank screen for half a second when rewriting info. Feel free to suggest optimizations!
I still need to look into memory usage, it feels kinda sluggish when the different zones are updating at once. But it works!

HTML5 canvas source-atop alternative in PIL

I cannot find the right function to add color overlay on another image when transparent areas should remain untouched.
For example on source image there is a semi-transparent white circle and around it everything is fully transparent. When I apply the operation with red color, I'd like to get white/red circle with fully transparent area around it.
On HTML5 canvas, it is achieved by:
layerContext.globalCompositeOperation = "source-atop";
layerContext.fillStyle = color;
layerContext.fillRect(0, 0, w, h);
I tried alpha_composite, blend and composite functions, but maybe misused them.
EDIT: here is what I get using:
overlay = Image.new('RGB', im1.size, (255, 0, 0))
im1.paste(overlay, mask=im1)
Canvas:
PIL:
PIL result is a little bit lighter, but color is the same. Any ideas?

Anti-Aliasing Images in a panel

I'm setting the shape of a wxPanel, but there are a lot of jagged edges. Is there a way to smooth these out?
Inside of the wx.Frame, I am setting the shape from a black and white .png image
mask = wx.Image('Resources/Images/Window/window_alpha_map.png')
mask.ConvertAlphaToMask()
shape = mask.ConvertToBitmap()
shape.SetMask(wx.Mask(shape, wx.BLACK))
self.SetShape(wx.RegionFromBitmap(shape))
Inside of the wxPanel, I am then setting the image in eraseBackground
def onEraseBackground(self, event):
dc = event.GetDC()
if not dc:
dc = wx.ClientDC(self)
rect = self.GetUpdateRegion().GetBox()
dc.SetClippingRect(rect)
dc.Clear()
background = wx.Image('Resources/Images/Window/window_background.png')
bmp = background.ConvertToBitmap()
dc.DrawBitmap(bmp, 0, 0)
Here are some examples of what I am talking about: http://clfu.se/PZn3 http://clfu.se/uE4
Is there a way to smooth these out in wxPython or even a trick in photoshop I am missing?
Any help is appreciated
You would probably be better off to just draw the image using a GraphicsContext widget as that supports anti-aliasing:
http://wxpython.org/Phoenix/docs/html/GraphicsContext.html
wxPython also allows drawing via Cairo, which also support anti-aliasing:
http://wiki.wxpython.org/UsingCairoWithWxPython
Finally, you could also take a look at FloatCanvas:
Best canvas for drawing in wxPython?

Pyglet: How to change resolution when you go fullscreen?

I'm using Pyglet and I have a little that includes an object moving over a background. Both of them are represented by images (png and jpg).
I've created a non-fullscreen window with size 800x600 and it works fine, but when I toggle to fullscreen... background and object have the same size as before and the rest of the screen is filled with black (empty color).
What I want to do is to "scale" the images or change the resolution when I toggle fullscreen mode.
I've read the documentation, but I can't find the answer to this.
I know that with Pygame, this problem solves itself automatically (if you change the window size, everything rescales automatically)... but how do you do this with pyglet?
This is my relevant code:
import pyglet
WIDTH = 800
HEIGHT = 600
working_dir = '/where/i/have/my/images/'
window = pyglet.window.Window(WIDTH, HEIGHT)
background = pyglet.image.load(working_dir + 'background.jpg')
flying_thing = pyglet.image.load(working_dir + 'flying_thing.png')
#window.event
def on_draw():
window.clear()
background.blit(0, 0)
flying_thing.blit(WIDTH // 2, HEIGHT // 2)
#window.event
def on_key_press(symbol, modifiers):
if symbol == pyglet.window.key.SPACE:
window.set_fullscreen(not window.fullscreen)
pyglet.app.run()
You can try this code changing working_dir, background.jpg and flying_thing.png to a working directory of yours and two images in it.
I didn't tried, but from pyglet docs, blit supports width and height. Its signature is
blit(self, x, y, z=0, width=None, height=None)
Have you tried using
background.blit(width=window.width, height=windows.height)
instead? (I'm not sure the window.width changes on full_screen, let's see...).
This answer can also be relevant to your question: https://stackoverflow.com/a/11183462/931303.

ImageFont's getsize() does not get correct text size?

I use the following two methods to to generate text preview image for a .ttf font file
PIL method:
def make_preview(text, fontfile, imagefile, fontsize=30):
try:
font = ImageFont.truetype(fontfile, fontsize)
text_width, text_height = font.getsize(text)
img = Image.new('RGBA', (text_width, text_height))
draw = ImageDraw.Draw(img)
draw.text((0, 0), text, font=font, fill=(0, 0, 0))
return True
except:
return False
ImageMagick method:
def make_preview(text, fontfile, imagefile, fontsize=30):
p = subprocess.Popen(['convert', '-font', fontfile, '-background',
'transparent', '-gravity', 'center', '-pointsize', str(fontsize),
'-trim', '+repage', 'label:%s' % text, image_file])
return p==0
Both methods create correct preview images most of time but in some rare cases (<2%), the font.getsize(text) just cannot get the correct text size which result in text overflowed provided canvas. ImageMagick has same problem.
Sample fonts and previews:
HANFORD.TTF
http://download.appfile.com/HANFORD.png
NEWTOW.TTF
http://download.appfile.com/NEWTOW.png
MILF.TTF
http://download.appfile.com/MILF.png
SWANSE.TTF
http://download.appfile.com/SWANSE.png
I have looked into ImageMagick's documentations and found the explanation of this problem at http://www.imagemagick.org/Usage/text/#overflow.
Is it possible to detect such text overflows and draw text to fit the canvas as we expected?
Not a programming solution, but when I regenerate your problem, its only happens on your fonts (other fonts like Arial is no problem at all), so I have fixed your font files (by changing ascent/decent metrics). you can download here,
And sorry about Hanford Script Font, its not perfect as you see, height seems ok, but left side is not get drawed, its out of my understanding.
UPDATE: Regarding Hanford Font, Here is a work around, pass extra space in text like " Handford Script", and then crop the extra space in image like img=img.crop(img.getbbox())
alt text http://img64.imageshack.us/img64/1903/hanfordfontworkaround.jpg
UPDATE2:I had to pass color=(255,255,255) in Image.New to get Black Text on White background
img = Image.new('RGBA', (text_width, text_height),color=(255,255,255))
I had a similar issue once in PHP and ImageMagick.
In the end, I solved this by drawing the text on a very large canvas, and then trimming it using the trim/auto-crop functions that shave extra space off the image.
If I understand your preview function right, it is actually already doing exactly that: It should be enough to just remove the width and height settings.
In this case, just specify ImageMagick to use a larger canvas size with a fixed font size and it will draw text at specified point size while keeping its integrity.
def make_preview(text, fontfile, imagefile, fontsize=30):
p = subprocess.call(['convert', '-font', fontfile, '-background',
'transparent', '-gravity', 'center', '-size', '1500x300',
'-pointsize', str(fontsize), '-trim', '+repage', 'label:%s' % text, image_file])
return p==0
If you need to fit text into specified canvas rather than using a fixed point size, you may need to resize the output image after it's created.
PIL doesn't do this very well drawing exotic fonts, no matter what point size you specify to load a font, it always overflows text outside output image.

Categories