Pyglet ignores alpha channel - python

I'm trying to make a level editor for a game I'm writing in Python using Pyglet. I want to be able to modify the darkness of any block I place. As such, I have added a darkness attribute to the Block class. Here is that class:
class Block:
def __init__(self, name, image, wall):
self.name = name
self.image = image
self.wall = wall
self.darkness = 0
def set_darkness(self, darkness):
self.darkness = darkness
def draw(self, x, y):
self.image.blit(x, y)
pyglet.graphics.draw(4, GL_QUADS, ('v2i', (x, y, x + block_width, y, x + block_width, y + block_height, x, y + block_width)), ('c4B', (0, 0, 0, self.darkness) * 4))
I am trying to darken an image by drawing a black rectangle on top of it, and then adjusting the transparency of that rectangle. However, no matter what I set self.darkness to in __init__, the rectangle continues to be completely opaque.
I tried enabling color blending with pyglet.gl by adding this code to the top of my program:
from pyglet.gl import *
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
This changed nothing. I also tried using Pyglet's SolidColorImagePattern class to create a black rectangle with the alpha channel set to self.darkness, but that also did nothing. How do I make this rectangle transparent?

For any OpenGL instruction, a valid and current OpenGL Context is of need. Of course pyglet provides an OpenGL context, but the context is created and made current when the pyglet (OpenGL) window is created (see pyglet.window).
Create the window, before setting up Blending to solve the issue:
from pyglet.gl import *
window = pyglet.window.Window(400, 400, "OpenGL window")
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)

Related

Pyglet render into texture

I'm writing a drawing program using pyglet, and I want to be able to have the image being created as separate from the window's buffer (for instance, the image could be larger than the window, or may want to draw to this image at a different rate than the main window is being re-drawn). I want to be able to draw into this off-screen image, then display it in the window, but pyglet doesn't allow drawing to anything else than a window. Is there any simple way I can do this?
I've tried creating a second hidden pyglet window, but this gets rendered at the same rate as the main window which I definitely don't want.
The closest I found was Pyglet draw text into texture, but the code there isn't complete, and also no longer works as the opengl version used by pyglet has moved on.
The following code works for me, perhaps someone else can improve on my answer:
import pyglet
from pyglet.gl import *
from ctypes import byref
W, H = 800, 600
image = pyglet.image.create(W, H)
texture = image.get_texture()
window = pyglet.window.Window(width=W, height=H)
fbo_id = gl.GLuint(0)
glGenFramebuffers(1, byref(fbo_id))
glBindFramebuffer(GL_FRAMEBUFFER, fbo_id)
glBindTexture(GL_TEXTURE_2D, texture.id)
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.id, 0)
rect1 = pyglet.shapes.Rectangle(0, 0, W, H, (255, 0, 0) )
rect2 = pyglet.shapes.Rectangle(W//4, H//4, W//2, H//2, (0, 0, 255) )
label = pyglet.text.Label("Hello World", font_name="Times New Roman", font_size=36,
x=W//2, y=H//2, anchor_x="center", anchor_y="center")
rect1.draw()
rect2.draw()
label.draw()
#window.event
def on_mouse_drag(x, y, dx, dy, xxx, modifiers):
glBindFramebuffer(GL_FRAMEBUFFER, fbo_id)
line = pyglet.shapes.Line(x-dx, y-dy, x, y, 3, (0, 255, 255))
line.draw()
#window.event
def on_draw():
glBindFramebuffer(GL_FRAMEBUFFER, 0)
window.clear()
texture.blit(0, 0)
pyglet.app.run()

Image in Button tkinter

I am having a problem in making a login image to a button. I have succeeded in making the image with a transparent background, but I can't succeed to make the button with transparent background.
I attached a screenshot that shows what I mean. The upper 'login' is a image (with transparent background), the lower is Login button but there is a white background around it. I want to make a button with transparent background.
self.login_image = Image.open('images/LoginButton.png')
self.login_image = ImageTk.PhotoImage(self.login_image)
self.main_screen_canvas.create_image(700, 300, image=self.login_image)
self.login_button = Button(self.main_screen_canvas, borderwidth=0, image=self.login_image)
self.login_button.place(x=300,y=400)
What should I do?
BackgroundImage
LoginButtonImage
Here's how to do what I was suggesting in the comment which uses the technique shown in another answer of mine to simulate a tkinter Button on a Canvas that has a transparent image placed on it (instead of text).
One issue I ran into was that fact that your 2421 × 1210 pixel background image was larger than my screen. To deal with it I added a fitrect() helper function to determine a new smaller size for it that would fit. I wrote it a long time ago, but have found it handy to have around many times (like now). Note that in the code ll and ur refer to the lower-left and upper-right corners of the rectangles involved.
Here's the resulting code:
from PIL import Image, ImageTk
import tkinter as tk
class CanvasButton:
""" Create left mouse button clickable canvas image object.
The x, y coordinates are relative to the top-left corner of the canvas.
"""
flash_delay = 100 # Milliseconds.
def __init__(self, canvas, x, y, image_source, command, state=tk.NORMAL):
self.canvas = canvas
if isinstance(image_source, str):
self.btn_image = tk.PhotoImage(file=image_source)
else:
self.btn_image = image_source
self.canvas_btn_img_obj = canvas.create_image(x, y, anchor='nw', state=state,
image=self.btn_image)
canvas.tag_bind(self.canvas_btn_img_obj, "<ButtonRelease-1>",
lambda event: (self.flash(), command()))
def flash(self):
self.set_state(tk.HIDDEN)
self.canvas.after(self.flash_delay, self.set_state, tk.NORMAL)
def set_state(self, state):
""" Change canvas button image's state.
Normally, image objects are created in state tk.NORMAL. Use value
tk.DISABLED to make it unresponsive to the mouse, or use tk.HIDDEN to
make it invisible.
"""
self.canvas.itemconfigure(self.canvas_btn_img_obj, state=state)
def fitrect(r1_ll_x, r1_ll_y, r1_ur_x, r1_ur_y, r2_ll_x, r2_ll_y, r2_ur_x, r2_ur_y):
""" Find the largest rectangle that will fit within rectangle r2 that has
rectangle r1's aspect ratio.
Note: Either the width or height of the resulting rect will be
identical to the corresponding dimension of rect r2.
"""
# Calculate aspect ratios of rects r1 and r2.
deltax1, deltay1 = (r1_ur_x - r1_ll_x), (r1_ur_y - r1_ll_y)
deltax2, deltay2 = (r2_ur_x - r2_ll_x), (r2_ur_y - r2_ll_y)
aspect1, aspect2 = (deltay1 / deltax1), (deltay2 / deltax2)
# Compute size of resulting rect depending on which aspect ratio is bigger.
if aspect1 > aspect2:
result_ll_y, result_ur_y = r2_ll_y, r2_ur_y
delta = deltay2 / aspect1
result_ll_x = r2_ll_x + (deltax2 - delta) / 2.0
result_ur_x = result_ll_x + delta
else:
result_ll_x, result_ur_x = r2_ll_x, r2_ur_x
delta = deltax2 * aspect1
result_ll_y = r2_ll_y + (deltay2 - delta) / 2.0
result_ur_y = result_ll_y + delta
return result_ll_x, result_ll_y, result_ur_x, result_ur_y
def btn_clicked():
""" Prints to console a message every time the button is clicked """
print("Button Clicked")
background_image_path = 'background_image.jpg'
button_image_path = 'button_image.png'
root = tk.Tk()
root.update_idletasks()
background_img = Image.open(background_image_path) # Must use PIL for JPG images.
scrnwidth, scrnheight = root.winfo_screenwidth(), root.winfo_screenheight()
bgrdwidth, bgrdheight = background_img.size
border_width, border_height = 20, 20 # Allow room for window's decorations.
# Determine a background image size that will fit on screen with a border.
bgr_ll_x, bgr_ll_y, bgr_ur_x, bgr_ur_y = fitrect(
0, 0, bgrdwidth, bgrdheight,
0, 0, scrnwidth-border_width, scrnheight-border_height)
bgr_width, bgr_height = int(bgr_ur_x-bgr_ll_x), int(bgr_ur_y-bgr_ll_y)
# Resize background image to calculated size.
background_img = ImageTk.PhotoImage(background_img.resize((bgr_width, bgr_height)))
# Create Canvas same size as fitted background image.
canvas = tk.Canvas(root, bd=0, highlightthickness=0, width=bgr_width, height=bgr_height)
canvas.pack(fill=tk.BOTH)
# Put background image on Canvas.
background = canvas.create_image(0, 0, anchor='nw', image=background_img)
# Put CanvasButton on Canvas centered at the bottom.
button_img = tk.PhotoImage(file=button_image_path)
btn_x, btn_y = (bgr_width/2), (bgr_height-button_img.height())
canvas_btn1 = CanvasButton(canvas, btn_x, btn_y, button_img, btn_clicked)
root.mainloop()
And here's the result of running it:

Pygame: How do I get rid of the black halo that is showing up on my planet images?

While testing my new-found python skills using pygame, I ran into an interesting problem.
When I load planet .png images onto the screen, the planets show up with a black halo.
This is really strange since the other images(.png and .bmp) that I have loaded into the game work without a problem.
I have imported the images by using a for loop and converting them with convert_alpha().
When I do not convert the images, the black halo is still there.
I also tried setting the color key with each iteration of the for loop to no avail:
class Pl_Images(pygame.sprite.Sprite):
def __init__(self):
self.pl_images = []
for num in range(1,5):
img = pygame.image.load(f"Images/planets/static/planet{num}.png")
img_rect = img.get_rect()
img = pygame.transform.scale(img, (int(img_rect.width//10), int(img_rect.height//10)))
self.pl_images.append(img)
One of the pre-loaded images are then used (when needed) by a Planet object when it is created by the planet generator class "PlanetSurface" and is placed into a sprite group.
import pygame
from planet import Planet
import random
from pygame.sprite import Sprite
from pl_images import Pl_Images
class PlanetSurface(Sprite):
def __init__(self, settings):
"""
Creates a planet surface and planets that travel across the screen
"""
super(PlanetSurface, self). __init__()
self.settings = settings
self.surface = pygame.surface.Surface(settings.screen.get_size())
self.surface.set_colorkey([0,0,0])
self.images = Pl_Images()
self.planets = pygame.sprite.Group()
self.pl_timer = random.randrange(420, 720) # 7 seconds and 12 seconds
self.max_planets = 3
self.current_num_planets = 0
def update(self):
""" update the planets """
self.planets.update()
for planet in self.planets.copy():
if planet.rect.y > self.settings.screen_height:
self.planets.remove(planet)
self.current_num_planets -= 1
if self.pl_timer == 0:
if self.current_num_planets >= self.max_planets:
pass
else:
self.new_planet = Planet(self.settings, self.images)
self.rect = self.new_planet.rect
self.planets.add(self.new_planet)
self.pl_timer = random.randrange(2100, 3600)
# Redraw planet for a smooth effect
self.surface.fill((0,0,0))
self.planets.draw(self.surface)
self.pl_timer -= 1
Here is the Planet class:
class Planet(Sprite):
def __init__(self, settings, images):
super(Planet, self). __init__()
self.images = images
self.planets = self.images.pl_images
self.settings = settings
self.num_planets = len(self.planets)
self.image_index = random.randrange(self.num_planets - 1)
self.image = self.planets[self.image_index]
self.rect = self.image.get_rect()
self.rect.x = random.randrange(
0,
self.settings.screen_width - self.rect.width
)
self.rect.y = -self.rect.height
self.vel_x = 0
self.vel_y = random.randrange(1, 2)
def update(self):
self.rect.x += self.vel_x
self.rect.y += self.vel_y
The image is then drawn to the planet's surface when the update method is called.
The surface that I am drawing the images to is a simple surface that uses a transparent colorkey:
This custom surface is being drawn by the main function on top of the main background but below the gaming surface:
self.screen.blit(self.background.bgimage, (self.background.bgX2,
self.background.bgY2))
self.screen.blit(self.planet_surface.surface, (0,0))
self.screen.blit(self.game_surface.surface, (0,0))
I am quite baffled by this problem since the planets are .png images with transparent backgrounds.
Does anyone have any idea where this black halo might be coming from?
Is there something that I am doing wrong or is this a glitch in pygame?
I have uploaded a sample planet for analysis.
planet45.png
Thanks in advance!
I examined the example planet you provided in your question. - planet45.png
The alpha channel of the entire image is 255. The image has an opaque white background and a pale blue halo around the planet. You can set a white color (set_colorkey()) to make the background transparent, but the halo will remain opaque. The problem is not with your application, but with the image resource. You need to use another planet image that provides alpha per pixel.
See also How can I make an Image with a transparent Backround in Pygame? or How to convert the background color of image to match the color of Pygame window? respectively How do I blit a PNG with some transparency onto a surface in Pygame?.

Named shapes in pyglet

Why do shapes and other objects in pyglet have to be named (as in assigned variables) ? The Rectangle below named "test" renders successfully, the one a line below does not. Why is that?
from pyglet import shapes
WHITE = (255, 255, 255)
HEIGHT = 1080
WIDTH = 720
window = pyglet.window.Window(HEIGHT, WIDTH)
background = pyglet.graphics.Batch()
test = shapes.Rectangle(200, 200, 50, 50, color=WHITE, batch=background) # Renders successfuly
shapes.Rectangle(300, 300, 50, 50, color=WHITE, batch=background) # Does not render
#window.event
def on_draw():
window.clear()
background.draw()
pyglet.app.run()
Here you are using pyglet.graphics.Batch() to render the shape objects together i.e. test(the first rectangle) + the second rectangle. A batch manages a set of objects that will be drawn at once, and hence to instantiate the objects that need to be drawn, they need to have a reference against which they can be mapped to that batch.
To draw a rectangle as is, without assigning it to a varible can be done by using the pyglet.graphics.draw() function directly by passing in the primitive type, rectanggle coordinates and formatstring.

I wanted to use object a class twice but it's not happening

basic.py
from OpenGL.GL import*
from OpenGL.GLU import*
from OpenGL.GLUT import*
import sys
class Draw:
# def draw_func(self):
# pass
def draw(self):
glutInit(sys.argv)
glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB)
glutInitWindowSize(1000,1000)
glutInitWindowPosition(50,50)
glutCreateWindow(b'Cycle')
glutDisplayFunc(self.draw_func)
glClearColor(0,0,0,1)
gluOrtho2D(-500,500,-500,100)
glutMainLoop()
cycle.py
class Circle(Draw):
def __init__(self,xv,yv):
self.xv=xv
self.yv= yv
def draw_circle(self):
glClear(GL_COLOR_BUFFER_BIT)
glColor3f(1, 0, 0)
glPointSize(3)
for t in arange(self.xv, self.yv, 0.001):
x = 50 * sin(t)
y = 50 * cos(t)
glBegin(GL_POINTS)
glVertex2f(x, y)
glEnd()
glFlush()
def draw_func(self):
self.draw_circle()
cycle = Circle(100,200)
cycle.draw()
cycle = Circle(220,300)
cycle.draw()
Here I am using pyOpenGl to learn graphics and I wanted to use the Circle object twice to draw 2 circle but it draws only one . I don't know where I am lacking my knowledge. Also please suggest me any tutorials to learn pyOpenGl I didn't find the good one.
The instruction
glClear(GL_COLOR_BUFFER_BIT)
clears the complete color plane of the frambuffer. This means you clear the frambuffer before you draw the 2nd circle.
Remove glClear(GL_COLOR_BUFFER_BIT) from the draw method and do it at the begin of the drawing instructions. Further glFlush should be done after the drawing instructions.
See glClear:
glClear sets the bitplane area of the window to values previously selected by glClearColor.
Your architecture is not proper. You should not derive Circle from Draw, but Draw should use Circle.
This means you have to create a class Draw which does the initializations and handles the main loop. The other separated class Circle draws a single circle:
class Circle:
def __init__(self,xv,yv):
self.xv=xv
self.yv= yv
def draw(self):
glColor3f(1, 0, 0)
glPointSize(3)
for t in arange(self.xv, self.yv, 0.001):
x = 50 * sin(t)
y = 50 * cos(t)
glBegin(GL_POINTS)
glVertex2f(x, y)
glEnd()
class Draw:
def __init__(self,cx,cy):
self.cx = cx
self.cy = cy
def draw(self):
glutInit(sys.argv)
glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB)
glutInitWindowSize(self.cx,self.cy)
glutInitWindowPosition(50,50)
glutCreateWindow(b'Cycle')
glutDisplayFunc(self.draw_loop)
glClearColor(0,0,0,1)
gluOrtho2D(-self.cx/2,self.cx/2,-self.cy/2,self.cy/2)
glutMainLoop()
def draw_loop(self):
glClear(GL_COLOR_BUFFER_BIT)
cycle = Circle(100,200)
cycle.draw()
cycle = Circle(220,300)
cycle.draw()
glFlush()
glutPostRedisplay()
wnd = Draw(1000,1000)
wnd.draw()

Categories