omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular

    Welcome!

    This is the community forum for my apps Pythonista and Editorial.

    For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.


    Using objective-c CGContextRef drawing in ui.ImageContext?

    Pythonista
    drawing objcutil context imagecontext
    4
    10
    6157
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • shinyformica
      shinyformica last edited by

      I have a need to do some drawing using some of the abilities that the objective-c CGContext api has available to it, but I am having trouble figuring out how to be able to make calls to the various CGContext-related functions.

      I found this old thread, which does some things with a graphics context:

      https://forum.omz-software.com/topic/2327/uiimagejpegrepresentation/1

      But it doesn't illustrate very clearly how it manages to successfully call functions like:

      UIGraphicsGetCurrentContext()
      
      UIGraphicsGetImageFromCurrentImageContext()
      

      It seems like I can call:

      context = objc_util.c.UIGraphicsGetCurrentContext()
      

      but if I try to use that context ref value in any CGContext draw function, like

      objc_util.c.CGContextFillRect(uicontext, r)
      

      it crashes hard. I was pretty sure that was invalid...but I'm not clear on how to call plain objective-c functions like this. There's examples of more complex creation/calling of functions in that thread I linked to above:

      def CGBitmapContextCreate(baseAddress, width, height, param_0, bytesPerRow, colorSpace, flags):
          func = c.CGBitmapContextCreate
          func.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_void_p, ctypes.c_int32]
          func.restype = ctypes.c_void_p
          print ObjCInstance(colorSpace).ptr
          result = func(baseAddress, width, height, param_0, bytesPerRow, ObjCInstance(colorSpace).ptr, flags)
          print result
          if result is not None:
              return result
          else:
              raise RuntimeError('Failed to create context')
      

      But I'm not clear on when/how/why you use that sort of setup.

      1 Reply Last reply Reply Quote 0
      • JonB
        JonB last edited by

        For the non objc functions (things inside objc_util.c, which are c functions and symbols), you almost always need to define argtypes and restype, unless everything is c_void_p.

        Check out https://github.com/zacbir/geometriq/tree/master/geometriq/backends _quartz.py and coregraphics.py, which have already have the functions defined, and many useful constants. Also, for those standard libraries, you can often search GitHub, and things like pyobjc, or pybee/Rubicon sometimes have wrappers written out. You can also use cffi, and pass in the actual headers.

        1 Reply Last reply Reply Quote 0
        • shinyformica
          shinyformica last edited by

          Thanks, that's what I was looking for.

          What is "cffi", exactly? A command-line to generate the info from the header somehow?

          1 Reply Last reply Reply Quote 0
          • JonB
            JonB last edited by JonB

            Cffi is a python module that parses c headers and generates the appropiate python functions/argtypes/restype . @dgelessus probably is the best reference for getting this to work in pythonista.
            httpss://github.com/dgelessus/pythonista-c-utils

            I tend to manually transcode stuff into ctypes - but it can be sometimes annoying to dig up all the various constants, etc

            1 Reply Last reply Reply Quote 0
            • shinyformica
              shinyformica last edited by

              @JonB nevermind, could've just googled it first. C Foreign Function Interface...cool stuff.

              1 Reply Last reply Reply Quote 0
              • shinyformica
                shinyformica last edited by

                Just for completeness, in case anyone reads through this thread:

                objc_util.c is the right place to access raw functions like this, of course, but as @JonB said, you have to be sure to set the right argtypes/restype for the function in order to call it successfully.

                So, for example, for my attempt to call UIGraphicsGetCurrentContext(), I needed:

                UIGraphicsGetCurrentContext = objc_util.c.UIGraphicsGetCurrentContext
                UIGraphicsGetCurrentContext.restype = ctypes.c_void_p
                UIGraphicsGetCurrentContext.argtypes = []

                which ensures that the function will return a reference to a C struct (in this case a CGContextRef), which will be a c_void_p type. The function takes no arguments, so an empty list is fine for argtypes.

                Now, to use that context reference to draw something in a pythonista ui.ImageContext, I can do something like:

                import ui
                import objc_util
                import ctypes
                
                UIGraphicsGetCurrentContext = objc_util.c.UIGraphicsGetCurrentContext
                UIGraphicsGetCurrentContext.restype = ctypes.c_void_p
                UIGraphicsGetCurrentContext.argtypes = []
                
                CGContextFillRect = objc_util.c.CGContextFillRect
                CGContextFillRect.restype = None
                CGContextFillRect.argtypes = [ctypes.c_void_p, objc_util.CGRect]
                
                def drawWithGraphicsContext():
                    image = None
                    with ui.ImageContext(300,300) as context:
                        path = ui.Path.rect(10,10,280,280)
                        path.line_width = 1
                        path.stroke()
                        c = UIGraphicsGetCurrentContext()
                        r = objc_util.CGRect(objc_util.CGPoint(0,0),objc_util.CGSize(200,200))
                        CGContextFillRect(c, r)
                        image = context.get_image()
                    return image
                
                m = drawWithGraphicsContext()
                m.show()
                

                and the lovely intermixing of pythonista and objc calls works fine.

                mikael cvp 2 Replies Last reply Reply Quote 0
                • mikael
                  mikael @shinyformica last edited by

                  @shinyformica, thanks for sharing! I am curious: what magical drawing tools are you going to be able to use now?

                  1 Reply Last reply Reply Quote 0
                  • cvp
                    cvp @shinyformica last edited by

                    @shinyformica the same for me 👍

                    1 Reply Last reply Reply Quote 0
                    • shinyformica
                      shinyformica last edited by shinyformica

                      @mikael, for the moment, I wanted to be able to use the nice gradient drawing functions that CGContext's provide. So, for example, I can draw a nice clean, fast, linear gradient between two colors with:

                      UIGraphicsGetCurrentContext = objc_util.c.UIGraphicsGetCurrentContext
                      UIGraphicsGetCurrentContext.restype = ctypes.c_void_p
                      UIGraphicsGetCurrentContext.argtypes = []
                      
                      CGColorSpaceCreateDeviceRGB = objc_util.c.CGColorSpaceCreateDeviceRGB
                      CGColorSpaceCreateDeviceRGB.restype = c_void_p
                      CGColorSpaceCreateDeviceRGB.argtypes = []
                      
                      CGGradientCreateWithColors = objc_util.c.CGGradientCreateWithColors
                      CGGradientCreateWithColors.restype = c_void_p
                      CGGradientCreateWithColors.argtypes = [c_void_p, c_void_p,
                                                              ctypes.POINTER(ctypes.c_float)]
                      
                      CGContextDrawLinearGradient = objc_util.c.CGContextDrawLinearGradient
                      CGContextDrawLinearGradient.restype = None
                      CGContextDrawLinearGradient.argtypes = [c_void_p, c_void_p,
                                                              objc_util.CGPoint, objc_util.CGPoint,
                                                              objc_util.NSUInteger]
                      
                      with ui.ImageContext(300,300) as context:
                              c = UIGraphicsGetCurrentContext()
                              colorSpace = CGColorSpaceCreateDeviceRGB()
                      
                              start = objc_util.UIColor.colorWithRed_green_blue_alpha_(1.0,0.0,0.0,1.0)
                              end = objc_util.UIColor.colorWithRed_green_blue_alpha_(0.0,0.0,1.0,1.0)
                              colors = [start.CGColor(), end.CGColor()]
                              gradient = CGGradientCreateWithColors(colorSpace, objc_util.ns(colors), None)
                              
                              CGContextDrawLinearGradient(c, gradient,
                                                      objc_util.CGPoint(0,0),
                                                      objc_util.CGPoint(300,0),
                                                      0)
                      

                      There's radial gradients as well, of course.

                      The above code works fine...but I did encounter one thing which I don't understand, and it's probably just because I don't fully get how python/ctypes/objc type conversion works...
                      In the above code, on this line:

                      gradient = CGGradientCreateWithColors(colorSpace, objc_util.ns(colors), None)
                      

                      it should be possible to set that "None" parameter to an array of floats representing the normalized locations of the colors in the gradient. However, the docs say that final parameter is passed in as "const float *" which meant I thought I had to do something like this:

                      c_float_p = ctypes.POINTER(ctypes.c_float)
                      locations = (ctypes.c_float * 2)(0.0,1.0)
                      gradient = CGGradientCreateWithColors(
                                              colorSpace, objc_util.ns(colors),
                                              ctypes.cast(locations, c_float_p))
                      

                      which should produce the same result as the previous version...but it doesn't, it ends up making a gradient with the second color coming first, and as a tiny sliver...so I must be mistaken in how I am creating and passing that locations parameter.

                      Here's the apple docs on that CGGradientCreateWithColors() function:
                      https://developer.apple.com/documentation/coregraphics/1398458-cggradientcreatewithcolors?language=objc

                      1 Reply Last reply Reply Quote 0
                      • shinyformica
                        shinyformica last edited by

                        In answer to my own question at the end there: that "locations" parameter is not a "const float *", it is actually "const CGFloat *" which can be 32 or 64 bits depending on platform architecture, and therefore a C float is not necessarily compatible...so I needed to explicitly use the CGFloat datatype:

                        CGFloat_p = ctypes.POINTER(objc_util.CGFloat)
                        CGGradientCreateWithColors = objc_util.c.CGGradientCreateWithColors
                        CGGradientCreateWithColors.restype = c_void_p
                        CGGradientCreateWithColors.argtypes = [c_void_p, c_void_p, CGFloat_p]
                        
                        locations = (objc_util.CGFloat * 2)(0.0,1.0)
                        gradient = CGGradientCreateWithColors(colorSpace, objc_util.ns(colors),  ctypes.cast(locations, CGFloat_p))
                        

                        and it works fine. As always, thanks all!

                        1 Reply Last reply Reply Quote 0
                        • First post
                          Last post
                        Powered by NodeBB Forums | Contributors