CIBevelSample
=============

* :download:`Download example <PyObjCExample-CIBevelSample.zip>`

A PyObjC Example without documentation

.. rst-class:: tabber

Sources
-------

.. rst-class:: tabbertab

CIBevelView.py
..............

.. sourcecode:: python

    from math import sin
    
    import objc
    import Quartz
    import Cocoa
    from objc import super  # noqa: A004
    from SampleCIView import SampleCIView
    
    NUM_POINTS = 4
    
    
    class CIBevelView(SampleCIView):
        currentPoint = objc.ivar(type=objc._C_INT)
        points = objc.ivar()
        angleTime = objc.ivar(type=objc._C_FLT)
        lineImage = objc.ivar()
        twirlFilter = objc.ivar()
        heightFieldFilter = objc.ivar()
        shadedFilter = objc.ivar()
    
        def initWithFrame_(self, frameRect):
            self = super().initWithFrame_(frameRect)
            if self is None:
                return None
    
            self.points = [None] * NUM_POINTS
            self.points[0] = Quartz.CGPointMake(
                0.5 * frameRect.size.width, frameRect.size.height - 100.0
            )
            self.points[1] = Quartz.CGPointMake(150.0, 100.0)
            self.points[2] = Quartz.CGPointMake(frameRect.size.width - 150.0, 100.0)
            self.points[3] = Quartz.CGPointMake(
                0.7 * self.points[0].x + 0.3 * self.points[2].x,
                0.7 * self.points[0].y + 0.3 * self.points[2].y,
            )
    
            url = Cocoa.NSURL.fileURLWithPath_(
                Cocoa.NSBundle.mainBundle().pathForResource_ofType_("lightball", "tiff")
            )
    
            self.lightball = Quartz.CIImage.imageWithContentsOfURL_(url)
    
            self.heightFieldFilter = Quartz.CIFilter.filterWithName_(
                "CIHeightFieldFromMask"
            )
            self.heightFieldFilter.setDefaults()
            self.heightFieldFilter.setValue_forKey_(15.0, "inputRadius")
    
            self.twirlFilter = Quartz.CIFilter.filterWithName_("CITwirlDistortion")
            self.twirlFilter.setDefaults()
            self.twirlFilter.setValue_forKey_(
                Quartz.CIVector.vectorWithX_Y_(
                    0.5 * frameRect.size.width, 0.5 * frameRect.size.height
                ),
                "inputCenter",
            )
            self.twirlFilter.setValue_forKey_(300.0, "inputRadius")
            self.twirlFilter.setValue_forKey_(0.0, "inputAngle")
    
            self.shadedFilter = Quartz.CIFilter.filterWithName_("CIShadedMaterial")
            self.shadedFilter.setDefaults()
            self.shadedFilter.setValue_forKey_(self.lightball, "inputShadingImage")
            self.shadedFilter.setValue_forKey_(20.0, "inputScale")
    
            # 1/30 second should give us decent animation
            Cocoa.NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(
                1.0 / 30.0, self, "changeTwirlAngle:", None, True
            )
            return self
    
        def changeTwirlAngle_(self, timer):
            self.angleTime += timer.timeInterval()
            self.twirlFilter.setValue_forKey_(
                -0.2 * sin(self.angleTime * 5.0), "inputAngle"
            )
            self.updateImage()
    
        def mouseDragged_(self, event):
            loc = self.convertPoint_fromView_(event.locationInWindow(), None)
            self.points[self.currentPoint].x = loc.x
            self.points[self.currentPoint].y = loc.y
            self.lineImage = None
    
            # normally we'd want this, but the timer will cause us to
            # redisplay anyway
            # self.setNeedsDisplay_(True)
    
        def mouseDown_(self, event):
            d = 1e4
            loc = self.convertPoint_fromView_(event.locationInWindow(), None)
            for i in range(NUM_POINTS):
                x = self.points[i].x - loc.x
                y = self.points[i].y - loc.y
                t = x * x + y * y
    
                if t < d:
                    self.currentPoint = i
                    d = t
    
            self.mouseDragged_(event)
    
        def updateImage(self):
            context = Cocoa.NSGraphicsContext.currentContext().CIContext()
            if self.lineImage is None:
                bounds = self.bounds()
                layer = context.createCGLayerWithSize_info_(
                    Quartz.CGSizeMake(Cocoa.NSWidth(bounds), Cocoa.NSHeight(bounds)), None
                )
    
                cg = Quartz.CGLayerGetContext(layer)
    
                Quartz.CGContextSetRGBStrokeColor(cg, 1, 1, 1, 1)
                Quartz.CGContextSetLineCap(cg, Quartz.kCGLineCapRound)
    
                Quartz.CGContextSetLineWidth(cg, 60.0)
                Quartz.CGContextMoveToPoint(cg, self.points[0].x, self.points[0].y)
                for i in range(1, NUM_POINTS):
                    Quartz.CGContextAddLineToPoint(cg, self.points[i].x, self.points[i].y)
                Quartz.CGContextStrokePath(cg)
    
                self.lineImage = Quartz.CIImage.alloc().initWithCGLayer_(layer)
    
            self.heightFieldFilter.setValue_forKey_(self.lineImage, "inputImage")
            self.twirlFilter.setValue_forKey_(
                self.heightFieldFilter.valueForKey_("outputImage"), "inputImage"
            )
    
            self.shadedFilter.setValue_forKey_(
                self.twirlFilter.valueForKey_("outputImage"), "inputImage"
            )
    
            self.setImage_(self.shadedFilter.valueForKey_("outputImage"))

.. rst-class:: tabbertab

SampleCIView.py
...............

.. sourcecode:: python

    """
    SampleCIView - simple OpenGL based CoreImage view
    """
    
    # XXX: Fix me
    # flake8: noqa F403, F405
    import CGL
    import Cocoa
    import objc
    import Quartz
    from OpenGL.GL import *
    from OpenGL.GL.APPLE.transform_hint import *
    
    # The default pixel format
    _pf = None
    
    
    class SampleCIView(Cocoa.NSOpenGLView):
        _context = objc.ivar()
        _image = objc.ivar()
        _lastBounds = objc.ivar(type=Cocoa.NSRect.__typestr__)
    
        @classmethod
        def defaultPixelFormat(self):
            global _pf
    
            if _pf is None:
                # Making sure the context's pixel format doesn't have a recovery
                # renderer is important - otherwise CoreImage may not be able to
                # create deeper context's that share textures with this one.
    
                attr = (
                    Cocoa.NSOpenGLPFAAccelerated,
                    Cocoa.NSOpenGLPFANoRecovery,
                    Cocoa.NSOpenGLPFAColorSize,
                    32,
                )
                _pf = Cocoa.NSOpenGLPixelFormat.alloc().initWithAttributes_(attr)
    
            return _pf
    
        def image(self):
            return self._image
    
        def setImage_dirtyRect_(self, image, r):
            if self._image is not image:
                self._image = image
    
                if Quartz.CGRectIsInfinite(r):
                    self.setNeedsDisplay_(True)
                else:
                    self.setNeedsDisplayInRect_(r)
    
        def setImage_(self, image):
            self.setImage_dirtyRect_(image, CGRectInfinite)
    
        def prepareOpenGL(self):
            param = 1
    
            # Enable beam-synced updates.
    
            self.openGLContext().setValues_forParameter_(
                (param,), Cocoa.NSOpenGLCPSwapInterval
            )
    
            # Make sure that everything we don't need is disabled. Some of these
            # are enabled by default and can slow down rendering.
    
            glDisable(GL_ALPHA_TEST)
            glDisable(GL_DEPTH_TEST)
            glDisable(GL_SCISSOR_TEST)
            glDisable(GL_BLEND)
            glDisable(GL_DITHER)
            glDisable(GL_CULL_FACE)
            glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
            glDepthMask(GL_FALSE)
            glStencilMask(0)
            glClearColor(0.0, 0.0, 0.0, 0.0)
            glHint(GL_TRANSFORM_HINT_APPLE, GL_FASTEST)
    
        def viewBoundsDidChange_(self, bounds):
            # For subclasses.
            pass
    
        def updateMatrices(self):
            r = self.bounds()
    
            if r != self._lastBounds:
                self.openGLContext().update()
    
                # Install an orthographic projection matrix (no perspective)
                # with the origin in the bottom left and one unit equal to one
                # device pixel.
    
                glViewport(0, 0, r.size.width, r.size.height)
    
                glMatrixMode(GL_PROJECTION)
                glLoadIdentity()
                glOrtho(0, r.size.width, 0, r.size.height, -1, 1)
    
                glMatrixMode(GL_MODELVIEW)
                glLoadIdentity()
    
                self._lastBounds = r
    
                self.viewBoundsDidChange_(r)
    
        def drawRect_(self, r):
            self.openGLContext().makeCurrentContext()
    
            # Allocate a CoreImage rendering context using the view's OpenGL
            # context as its destination if none already exists.
    
            if self._context is None:
                pf = self.pixelFormat()
                if pf is None:
                    pf = type(self).defaultPixelFormat()
    
                self._context = Quartz.CIContext.contextWithCGLContext_pixelFormat_options_(
                    CGL.CGLGetCurrentContext(), pf.CGLPixelFormatObj(), None
                )
    
            ir = Cocoa.CGRectIntegral(r)
    
            if Cocoa.NSGraphicsContext.currentContextDrawingToScreen():
                self.updateMatrices()
    
                # Clear the specified subrect of the OpenGL surface then
                # render the image into the view. Use the GL scissor test to
                # clip to * the subrect. Ask CoreImage to generate an extra
                # pixel in case * it has to interpolate (allow for hardware
                # inaccuracies)
    
                rr = Cocoa.CGRectIntersection(
                    Cocoa.CGRectInset(ir, -1.0, -1.0), self._lastBounds
                )
    
                glScissor(ir.origin.x, ir.origin.y, ir.size.width, ir.size.height)
                glEnable(GL_SCISSOR_TEST)
    
                glClear(GL_COLOR_BUFFER_BIT)
    
                if self.respondsToSelector_("drawRect:inCIContext:"):
                    self.drawRect_inCIContext_(rr, self._context)
    
                elif self._image is not None:
                    self._context.drawImage_atPoint_fromRect_(self._image, rr.origin, rr)
    
                glDisable(GL_SCISSOR_TEST)
    
                # Flush the OpenGL command stream. If the view is double
                # buffered this should be replaced by [[self openGLContext]
                # flushBuffer].
    
                glFlush()
    
            else:
                # Printing the view contents. Render using CG, not OpenGL.
    
                if self.respondsToSelector_("drawRect:inCIContext:"):
                    self.drawRect_inCIContext_(ir, self._context)
    
                elif self._image is not None:
                    cgImage = self._context.createCGImage_fromRect_(self._image, ir)
    
                    if cgImage is not None:
                        Quartz.CGContextDrawImage(
                            Cocoa.NSGraphicsContext.currentContext().graphicsPort(),
                            ir,
                            cgImage,
                        )

.. rst-class:: tabbertab

main.py
.......

.. sourcecode:: python

    import CIBevelView  # noqa: F401
    import SampleCIView  # noqa: F401
    from PyObjCTools import AppHelper
    
    AppHelper.runEventLoop()

.. rst-class:: tabbertab

setup.py
........

.. sourcecode:: python

    """
    Script for building the example.
    
    Usage:
        python3 setup.py py2app
    """
    
    from setuptools import setup
    
    setup(
        name="CIBevelSample",
        app=["main.py"],
        data_files=["English.lproj", "lightball.tiff"],
        setup_requires=["py2app", "pyobjc-framework-Cocoa", "pyobjc-framework-Quartz"],
    )

