-
Notifications
You must be signed in to change notification settings - Fork 45
Colors expected from wx versus skia's with CPU or raster backend #323
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
I think my thoughts are along the same line as I was:
|
@prashant-saxena I see it should be possible to prepare the cpusurface from the beginning with the byte order you want: can you modify the line |
I think the first argument is probably not important. So you could do |
Since you clearly want RGBA in |
I.e. I am suggesting you just removing any numpy dependent code, and start from what wx needs, and configure skia to deliver it. |
Okay, I have got answer: If you replace
And just delete all the numpy related code, it would draw a blue square. The probably with your code(?) Is that by default, I think surface should have a method that does this in one step (without creating a throw-away surface), but I haven't found it yet. |
Okay, there is a one line version:
This probably should be a new constructor for Surface, just taking an |
I guess the main problem is that what exactly skia does is not obvious/ documented, and the correct way to match what you intend (wx has only |
This isn't the same as #191. I think this should be resolved with better documentation (you need to check |
I have changed the title to better reflect the problem: you have wx which expects one color byte order, versus what skia provides by default (but configurable). |
…ftware Reference to drawing with wx. See kyamagu#323 Update wording
Final part of fixing kyamagu#323
@prashant-saxena for now, this longish one-liner is the answer:
#324 includes better documentation, plus letting you do: Btw, this change will land in v138 . We have had about 20 v1xx now - the beta is really about v1xx being ~93% compatible with v87, not 100%, nothing to do with bugs or what not. You should upgrade etc if you are starting a new project: v87 is about 5 years old. If you use skia-python for anything serious / corporate, or just feel like being appreciative, there is a sponsor button underneath my profile for sponsor / commission the work, and the last few percent of compatibility with v87. |
Thanks @HinTak. The problem has been resolved without any performance issues. BTW from where I can install v138? |
You can download a v136 + this enhancement at https://github.com/kyamagu/skia-python/actions/runs/14894720347 - get the windows python 3.10-3.13 zip and extract the 3.10.wheel, then install with pip - maybe you need the Upstream release is about a month apart, m137 is already in #321 . These days we do bimonthly release here in skia-python, so v138 would be out in about a month's time, when upstream does m138. Btw, your code seems to have two issues - it seems to be doing drawing over and over - argh, I think the Refresh on timer is wrong - the other thing is, I like it to fail right away if I mistype something, so I added some assert, but it won't fail, and continues to "fail to draw" over and over, and piling up error messages, until I interrupt it. But I don't know wx at all (I only just installed it on Linux yesterday, to debug this issue) so I am not going to look at wx-related side issues. |
@prashant-saxena and a friendly request - you mentioned you had a wx + skia wirh GL working? I have a collection of skia using OpenGL under glfw, glut, and sdl2 at (here is the sdl2 version) If you can write a small example of skia using opengl under wx - ideally a And yes, there is a sponsor button under my profile if you feel like buying me a coffee or more. |
SKIA-WX-CPU With pan & zoom to point using wheel # python imports
import math
import ctypes
# pip imports
import wx
import skia
"""Enable high-res displays."""
try:
ctypes.windll.shcore.SetProcessDpiAwareness(1)
ctypes.windll.shcore.SetProcessDpiAwareness(2)
except Exception:
pass # fail on non-windows
class SkiaCPUCanvas(wx.Panel):
"""A cpu based skia canvas"""
def __init__(self, parent, size):
super().__init__(parent, size=size)
# or else we'll have a flicker
self.SetBackgroundStyle(wx.BG_STYLE_PAINT)
self.is_dragging = False
self.last_mouse_pos = (0.0, 0.0)
self.offset_x = 0.0
self.offset_y = 0.0
self.zoom = 1.0
self.Bind(wx.EVT_LEFT_DOWN, self.on_mouse_left_down)
self.Bind(wx.EVT_LEFT_UP, self.on_mouse_left_up)
self.Bind(wx.EVT_MOTION, self.on_mouse_move)
self.Bind(wx.EVT_MOUSEWHEEL, self.on_mouse_wheel)
self.Bind(wx.EVT_SIZE, self.on_size)
self.Bind(wx.EVT_PAINT, self.on_paint)
def on_mouse_left_down(self, event):
self.is_dragging = True
self.last_mouse_pos = (event.GetX(), event.GetY())
event.Skip()
def on_mouse_left_up(self, event):
self.is_dragging = False
event.Skip()
def on_mouse_move(self, event):
if self.is_dragging:
xpos, ypos = event.GetX(), event.GetY()
lx, ly = self.last_mouse_pos
dx, dy = xpos - lx, ypos - ly
self.offset_x += dx / self.zoom
self.offset_y += dy / self.zoom
self.last_mouse_pos = (xpos, ypos)
self.Refresh()
event.Skip()
def on_mouse_wheel(self, event):
"""Implement zoom to point"""
x, y = event.GetX(), event.GetY()
rotation = event.GetWheelRotation()
if rotation > 1:
zoom_factor = 1.1
elif rotation < -1:
zoom_factor = 0.9
else:
return
old_zoom = self.zoom
new_zoom = self.zoom * zoom_factor
# Convert screen coords to centered canvas coords
cx, cy = self.size[0] / 2, self.size[1] / 2
dx = x - cx
dy = y - cy
# World coords under mouse before zoom
world_x = (dx / old_zoom) - self.offset_x
world_y = (dy / old_zoom) - self.offset_y
# Apply zoom
self.zoom = new_zoom
# Adjust offset so world point stays under cursor
self.offset_x = (dx / self.zoom) - world_x
self.offset_y = (dy / self.zoom) - world_y
self.Refresh()
def on_paint(self, event):
w, h = self.GetSize()
if w == 0 or h == 0:
return
self.draw(w, h)
image = self.surface.makeImageSnapshot()
bitmap = wx.Bitmap.FromBufferRGBA(w, h, image.tobytes())
dc = wx.PaintDC(self)
dc.DrawBitmap(bitmap, 0, 0)
def draw(self, w, h):
sw, h = self.GetSize()
if w == 0 or h == 0:
return
self.canvas.clear(skia.ColorWHITE)
self.canvas.save()
# Set up the translation and zoom (pan/zoom)
self.canvas.translate(w / 2, h / 2)
self.canvas.scale(self.zoom, self.zoom)
self.canvas.translate(self.offset_x, self.offset_y)
# Create a solid paint (Blue color, but explicitly defining RGBA)
paint = skia.Paint(AntiAlias=True, Color=skia.Color(255, 0, 0),)
# Draw a series of circles in a spiral pattern
for i in range(150):
angle = i * math.pi * 0.1
x = math.cos(angle) * i * 3
y = math.sin(angle) * i * 3
# Draw solid circles
self.canvas.drawCircle(x, y, 4 + (i % 4), paint)
self.canvas.restore()
def on_size(self, event):
wx.CallAfter(self.set_size)
event.Skip()
def set_size(self):
# Actual drawing area (without borders) is GetClientSize
size = self.GetClientSize()
# On HiDPI screens, this could be 1.25, 1.5, 2.0, etc.
scale = self.GetContentScaleFactor()
width = int(size.width * scale)
height = int(size.height * scale)
self.size = wx.Size(width, height)
self.surface = skia.Surface.MakeRaster(skia.ImageInfo.MakeN32Premul(
width, height).makeColorType(skia.ColorType.kRGBA_8888_ColorType))
self.canvas = self.surface.getCanvas()
self.Refresh()
class MainFrame(wx.Frame):
def __init__(self):
super().__init__(None, title="Skia Wx CPU Canvas", size=(800, 600))
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
self.canvas = SkiaCPUCanvas(panel, (800, 600))
sizer.Add(self.canvas, 1, wx.EXPAND)
panel.SetSizer(sizer)
self.Show()
if __name__ == "__main__":
app = wx.App(False)
frame = MainFrame()
frame.Show()
app.MainLoop() |
SKIA-WX-GPU With pan & zoom to point using wheel # python imports
import math
import ctypes
# pip imports
import wx
from wx import glcanvas
import skia
import moderngl
GL_RGBA8 = 0x8058
"""Enable high-res displays."""
try:
ctypes.windll.shcore.SetProcessDpiAwareness(1)
ctypes.windll.shcore.SetProcessDpiAwareness(2)
except Exception:
pass # fail on non-windows
class SkiaWxGPUCanvas(glcanvas.GLCanvas):
"""A skia based GlCanvas"""
def __init__(self, parent, size):
glcanvas.GLCanvas.__init__(self, parent, -1, size=size)
self.glctx = glcanvas.GLContext(self) # ✅ Correct GLContext
self.size = size
self.ctx = None
self.canvas = None
self.surface = None
self.is_dragging = False
self.last_mouse_pos = (0.0, 0.0)
self.offset_x = 0.0
self.offset_y = 0.0
self.zoom = 1.0
self.Bind(wx.EVT_LEFT_DOWN, self.on_mouse_left_down)
self.Bind(wx.EVT_LEFT_UP, self.on_mouse_left_up)
self.Bind(wx.EVT_MOTION, self.on_mouse_move)
self.Bind(wx.EVT_MOUSEWHEEL, self.on_mouse_wheel)
self.Bind(wx.EVT_PAINT, self.on_paint)
self.Bind(wx.EVT_SIZE, self.on_size)
# Do nothing, to avoid flashing on MSW.
self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: None)
def init_gl(self):
"""Initialize Skia GPU context and surface."""
if self.ctx is None:
self.ctx = moderngl.create_context()
context = skia.GrDirectContext.MakeGL()
backend_render_target = skia.GrBackendRenderTarget(
self.size[0], self.size[1], 0, 0, skia.GrGLFramebufferInfo(
0, GL_RGBA8)
)
self.surface = skia.Surface.MakeFromBackendRenderTarget(
context, backend_render_target, skia.kBottomLeft_GrSurfaceOrigin,
skia.kRGBA_8888_ColorType, skia.ColorSpace.MakeSRGB()
)
self.canvas = self.surface.getCanvas()
def on_paint(self, event):
"""Handle drawing."""
self.SetCurrent(self.glctx)
if self.canvas is None:
self.init_gl()
# This is your actual skia based drawing function
self.on_draw()
self.SwapBuffers()
def on_mouse_left_down(self, event):
self.is_dragging = True
self.last_mouse_pos = (event.GetX(), event.GetY())
event.Skip()
def on_mouse_left_up(self, event):
self.is_dragging = False
event.Skip()
def on_mouse_move(self, event):
if self.is_dragging:
xpos, ypos = event.GetX(), event.GetY()
lx, ly = self.last_mouse_pos
dx, dy = xpos - lx, ypos - ly
self.offset_x += dx / self.zoom
self.offset_y += dy / self.zoom
self.last_mouse_pos = (xpos, ypos)
self.Refresh()
event.Skip()
def on_mouse_wheel(self, event):
"""Implement zoom to point"""
x, y = event.GetX(), event.GetY()
rotation = event.GetWheelRotation()
if rotation > 1:
zoom_factor = 1.1
elif rotation < -1:
zoom_factor = 0.9
else:
return
old_zoom = self.zoom
new_zoom = self.zoom * zoom_factor
# Convert screen coords to centered canvas coords
cx, cy = self.size[0] / 2, self.size[1] / 2
dx = x - cx
dy = y - cy
# World coords under mouse before zoom
world_x = (dx / old_zoom) - self.offset_x
world_y = (dy / old_zoom) - self.offset_y
# Apply zoom
self.zoom = new_zoom
# Adjust offset so world point stays under cursor
self.offset_x = (dx / self.zoom) - world_x
self.offset_y = (dy / self.zoom) - world_y
self.Refresh()
def on_draw(self):
"""Draw on Skia canvas."""
w, h = self.GetSize()
if w == 0 or h == 0:
return
self.ctx.viewport = (0, 0, self.size.width, self.size.height)
self.canvas.clear(skia.ColorWHITE)
self.canvas.save()
# Set up the translation and zoom (pan/zoom)
self.canvas.translate(w / 2, h / 2)
self.canvas.scale(self.zoom, self.zoom)
self.canvas.translate(self.offset_x, self.offset_y)
# Create a solid paint (Blue color, but explicitly defining RGBA)
paint = skia.Paint(AntiAlias=True, Color=skia.Color(255, 0, 0),)
# Draw a series of circles in a spiral pattern
for i in range(150):
angle = i * math.pi * 0.1
x = math.cos(angle) * i * 3
y = math.sin(angle) * i * 3
# Draw solid circles
self.canvas.drawCircle(x, y, 4 + (i % 4), paint)
self.canvas.restore()
self.surface.flushAndSubmit()
def on_size(self, event):
"""Handle resizing of the canvas."""
wx.CallAfter(self.set_viewport)
event.Skip()
def set_viewport(self):
# Actual drawing area (without borders) is GetClientSize
size = self.GetClientSize()
# On HiDPI screens, this could be 1.25, 1.5, 2.0, etc.
scale = self.GetContentScaleFactor()
width = int(size.width * scale)
height = int(size.height * scale)
self.size = wx.Size(width, height)
if self.ctx:
self.SetCurrent(self.glctx)
self.ctx.viewport = (0, 0, width, height)
self.init_gl()
class MainFrame(wx.Frame):
def __init__(self):
super().__init__(None, title="Skia WxPython GPU Canvas", size=(800, 600))
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
self.canvas = SkiaWxGPUCanvas(panel, (800, 600))
sizer.Add(self.canvas, 1, wx.EXPAND)
panel.SetSizer(sizer)
self.Show()
if __name__ == "__main__":
app = wx.App(False)
frame = MainFrame()
frame.Show()
app.MainLoop() |
Thanks. I like to point out that the GL version has a Btw, I don't think you need to involve |
I have made issue-214-wx.py myself -added some glcontextattrs which is thought to be important on mac os x : https://github.com/HinTak/skia-python-examples/blob/main/issue-214-wx.py it is meant to be a long smaller. I have simplified your wx code and removed all the unnecessary material. Can you give this a try to see if it still works? It is suppose to just put two drawings on a black (colourless) background, like in #214 I have removed the dependency on modern GL, and put yours here: |
In both scripts you can remove the dependency of OpenGL. You don't need all these. Code is working without them. from OpenGL.GL import glViewport, glClear, GL_COLOR_BUFFER_BIT
glViewport(0, 0, self.size.width, self.size.height)
glClear(GL_COLOR_BUFFER_BIT) You just need this. That's all. The viewport logic is correct. If you try to get the size of the client in the on_size method, you would be getting 800x600, which |
I meant what you had originally, this part in set_viewport which depends on moderngl, looks wrong.
So you were checking that it is not None, then immediately check it is None, before I remove moderngl. The app only receives exactly one I happened to think getting a constant from Clearing the buffer before drawing is a good practice - you know what you start with. I'll put the wx GLContextAttrs part in skia-python docs as "possibly needed for mac os x", like the other sdl2 / glfw suggestions. |
Describe the bug
Colors are flipped. RGB -> BGR
You should suppose to see a blue color square not red.
This issue has been discussed earlier #191 but no clear explanation of the behavior.
To Reproduce
Steps to reproduce the behavior:
Expected behavior
skia.Color() should create RGB color not BGR.
Desktop (please complete the following information):
Additional context
This behavior is only on CPU based canvas. On OpenGL based canvas there are no issues.
I can use either of these to solve the problem but the performance is really going down when working with large resolution 1920x1000 and implementing operations such as pan or zoom.
The text was updated successfully, but these errors were encountered: