Skip to content

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

Open
prashant-saxena opened this issue May 7, 2025 · 20 comments
Open

Comments

@prashant-saxena
Copy link

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:

# python imports
import ctypes
# pip imports
import numpy as np
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.Bind(wx.EVT_SIZE, self.on_size)
        self.Bind(wx.EVT_PAINT, self.on_paint)
        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.on_timer, self.timer)
        self.timer.Start(32)  # 1000/32 = 31.25 FPS

    def bgra_to_rgba(self, pixels: bytes, width: int, height: int) -> bytes:
        arr = np.frombuffer(pixels, dtype=np.uint8).reshape((height, width, 4))
        arr = arr[:, :, [2, 1, 0, 3]]  # Swap BGR -> RGB, keep Alpha
        return arr.tobytes()

    def on_paint(self, event):
        w, h = self.GetSize()
        if w == 0 or h == 0:
            return

        self.draw(w, h)

        image = self.surface.makeImageSnapshot()
        # image = image.toarray(colorType=skia.ColorType.kRGBA_8888_ColorType).tobytes()
        # pixels_rgba = self.bgra_to_rgba(image.tobytes(), w, h)
        bitmap = wx.Bitmap.FromBufferRGBA(w, h, image.tobytes())

        dc = wx.PaintDC(self)
        dc.DrawBitmap(bitmap, 0, 0)

    def draw(self, w, h):
        self.canvas.clear(skia.ColorBLACK)  # Clear the canvas with white background
        self.canvas.save()
        self.canvas.translate(w / 2, h / 2)

        paint = skia.Paint(AntiAlias=True, Color=skia.Color(0, 0, 255)) # BLUE COLOR
        self.canvas.drawRect(skia.Rect(0, 0, 100, 100), 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(width, height)
        self.canvas = self.surface.getCanvas()

        self.Refresh()

    def on_timer(self, event):
        self.Refresh()


class MainFrame(wx.Frame):
    def __init__(self):
        super().__init__(None, title="Skia 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()

Expected behavior
skia.Color() should create RGB color not BGR.

Desktop (please complete the following information):

  • OS: Windows 10
  • Python: 3.10.0
  • skia-python version: 87.8
  • wxpython: 4.2.3

Additional context
This behavior is only on CPU based canvas. On OpenGL based canvas there are no issues.

image = image.toarray(colorType=skia.ColorType.kRGBA_8888_ColorType).tobytes()
pixels_rgba = self.bgra_to_rgba(image.tobytes(), w, h)

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.

@HinTak
Copy link
Collaborator

HinTak commented May 7, 2025

I think my thoughts are along the same line as I was:

  • the from... and to... should ideally defaults to work round-trip ... I think this is not the case at the moment?
  • it can get a bit complicated endian-wise, as different platform/tech follows different conventions: some just used "platform order" (whatever is fastest / convenient), some actually defines a spec'ed byte order regarded less of platform
  • if round-trip isn't the case, I think we need to add "byte order I want" in both. At the moment it is missing in one of them?
  • there is also the issue about breaking existing behavior, which others might depend on... so I think perhaps adding precise "byte order I want" option to all the from... and to... might be best, or in any case, useful.

@HinTak
Copy link
Collaborator

HinTak commented May 7, 2025

@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 skia.Surface(width, height) to skia.Surface(width, height, props), where props = skia.SurfaceProps(a, b), and a = skia.SurfaceProps.Flags.kUseDeviceIndependentFonts_Flag and b = skia.PixelGeometry.kRGB_H_PixelGeometry or kBGR_H_PixelGeometry ? And play with the Flags.... and PixelGeometery.... parts to see if you can get acceptable performance?

@HinTak
Copy link
Collaborator

HinTak commented May 7, 2025

I think the first argument is probably not important. So you could do props = skia.SurfaceProps() ; props.pixelGeometry = skia.PixelGeometry.kRGB_H_PixelGeometry; before you pass it into skia.Surface?

@HinTak
Copy link
Collaborator

HinTak commented May 7, 2025

Since you clearly want RGBA in wx.Bitmap.FromBufferRGBA, you probably should use skia.PixelGeometry.kRGB_H_PixelGeometry or skia.PixelGeometry.kRGB_V_PixelGeometry . I don't know the difference, but give that a try.

@HinTak
Copy link
Collaborator

HinTak commented May 7, 2025

I.e. I am suggesting you just removing any numpy dependent code, and start from what wx needs, and configure skia to deliver it.

@HinTak
Copy link
Collaborator

HinTak commented May 7, 2025

Okay, I have got answer:

If you replace self.surface = skia.Surface(width, height) with two lines:

surface1 = skia.Surface(width, height)
self.surface = surface1.makeSurface(skia.ImageInfo.MakeN32Premul(width, height).makeColorType(skia.ColorType.kRGBA_8888_ColorType))

And just delete all the numpy related code, it would draw a blue square. The probably with your code(?) Is that by default, surface1.imageInfo().colorType() is BGRA . And you don't have a wx.Bitmap.FromBufferBGRA method. I think this should work and fast as it is only done once at the beginning (make a surface, then create another which is more or less the same as the first but with a different color info).

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.

@HinTak
Copy link
Collaborator

HinTak commented May 7, 2025

Okay, there is a one line version:

self.surface = skia.Surface.MakeRaster(skia.ImageInfo.MakeN32Premul(width, height).makeColorType(skia.ColorType.kRGBA_8888_ColorType))

This probably should be a new constructor for Surface, just taking an ImageInfo argument.

@HinTak
Copy link
Collaborator

HinTak commented May 7, 2025

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 wx.Bitmap.FromBufferRGBA so it is obvious what it wants). I'd say this is poor documentation / API design, but we can't quite change current behavior..
So I 'd probably say we should better document this, as well as provide a slightly prettier constructor skia.Surface(width, height, colortype.byteorder) than the long MakeRaster call.

@HinTak
Copy link
Collaborator

HinTak commented May 7, 2025

This isn't the same as #191. I think this should be resolved with better documentation (you need to check surface.imageInfo().colorType() matching how it is used later, feeding into wx.Bitmap.FromBufferRGBA)
& and a better / shorter constructor for a CPU-based surface which can do the matching.

@HinTak HinTak changed the title Colors are flipped on Windows with CPU or raster backend Colors expected from wx versus skia's with CPU or raster backend May 7, 2025
@HinTak
Copy link
Collaborator

HinTak commented May 7, 2025

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).

HinTak added a commit to HinTak/skia-python that referenced this issue May 7, 2025
…ftware

Reference to drawing with wx.

See kyamagu#323

Update wording
HinTak added a commit to HinTak/skia-python that referenced this issue May 7, 2025
HinTak added a commit to HinTak/skia-python that referenced this issue May 7, 2025
@HinTak
Copy link
Collaborator

HinTak commented May 7, 2025

@prashant-saxena for now, this longish one-liner is the answer:

Okay, there is a one line version:

self.surface = skia.Surface.MakeRaster(skia.ImageInfo.MakeN32Premul(width, height).makeColorType(skia.ColorType.kRGBA_8888_ColorType))

This probably should be a new constructor for Surface, just taking an ImageInfo argument.

#324 includes better documentation, plus letting you do:
skia.Surface(skia.ImageInfo.MakeN32Premul(width, height).makeColorType(skia.ColorType.kRGBA_8888_ColorType)) and
skia.Surface(width, height, skia.ColorType.kRGBA_8888_ColorType) . I assume you'll want to do the latter.

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.

@prashant-saxena
Copy link
Author

Thanks @HinTak. The problem has been resolved without any performance issues. BTW from where I can install v138?

@HinTak
Copy link
Collaborator

HinTak commented May 8, 2025

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 --pre option to get it to install. As I said it is about 93% compatible with v87, so YMMV - you may need to read https://github.com/kyamagu/skia-python/blob/main/relnotes/README.m116.md if you encounter issues running code written for v87. The difference is smaller later, and the other README.m1xx covers the later changes.

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.

@HinTak
Copy link
Collaborator

HinTak commented May 8, 2025

@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)
https://github.com/HinTak/skia-python-examples/blob/main/issue-214-sdl2.py

If you can write a small example of skia using opengl under wx - ideally a issue-214-wx.py, just replacing the glfw/glut / wx toolkit code with wx-specific ones, that would be cool.

And yes, there is a sponsor button under my profile if you feel like buying me a coffee or more.

@prashant-saxena
Copy link
Author

prashant-saxena commented May 8, 2025

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()

@prashant-saxena
Copy link
Author

prashant-saxena commented May 8, 2025

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()

@HinTak
Copy link
Collaborator

HinTak commented May 8, 2025

Thanks. I like to point out that the GL version has a GL_RGBA8 argument to the setup, which is the equivalent of the CPU directive we discussed here.

Btw, I don't think you need to involve moderngl - I just need the from wx import glcanvas line and what it follows, really. I believe skia should be able to auto detect glcanvas without involving moderngl, maybe with a few more glcanvas methods. I'll see if I can write a issue-214-wx.py myself.

HinTak added a commit to HinTak/skia-python-examples that referenced this issue May 8, 2025
HinTak added a commit to HinTak/skia-python-examples that referenced this issue May 8, 2025
@HinTak
Copy link
Collaborator

HinTak commented May 9, 2025

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:
https://github.com/HinTak/skia-python-examples/blob/main/SKIA-WX-GPU.py - I think the on_size / viewport logic is a bit wrong, but since it is not actually resizable, probably not worth fixing.

@prashant-saxena
Copy link
Author

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.
GL_RGBA8 = 0x8058

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
is not the actual size of the drawing area. it would be little less (with out borders, title bar etc). A fractional time is required for some internal initialization before you would receive actual area. This is not required in normal applications but in my case I really need the correct area or else I won't be able do correct hit testing.

@HinTak
Copy link
Collaborator

HinTak commented May 9, 2025

I meant what you had originally, this part in set_viewport which depends on moderngl, looks wrong.

        if self.ctx:
            self.SetCurrent(self.glctx)
            self.ctx.viewport = (0, 0, width, height)
            self.init_gl()

init_gl also plays with ctx:

        if self.ctx is None:
            self.ctx = moderngl.create_context()

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 on_size event, right at the start, and is effectively not-resizeable , so setting viewport isn't really needed, but for other usage, it might be useful. Yes, I had a quick look at how the events are fired, on_size seems to be run once at beginning after frame.Show but you can't put init_gl inside frame.__init__. Some of it feels a bit strange compared to glfw & sdl2, but maybe I am just not familiar with wx to do it correctly in a "obviously correct" way. Having to check if self.ctx / if not self.ctx in more than one place look wrong, that's how I feel. Hence how issue-214-wx.py is written. The original #214 is about quirks in mac os x so I had a look around the wx equivalent for those mac os x settings, that's the main purpose.

I happened to think getting a constant from
from OpenGL.GL is better than just writing it down - in principle GL_RGBA8 is a constant that skia and opengl have to agree, and it is from opengl headers (and the opengl specification), although skia has the value inside skia header too, just not available in skia-python (many part of skia is not available in skia-python, that's normal...).

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants