# CGShading Demo

- `Download example`

A PyObjC Example without documentation

## Sources

### MyQuartzView.py

    """
    Example for using CGShading and CGFunction. This code is directly translated
    from procedural C code and is definitely not good Python style.
    """

    import array
    import math
    import random
    import sys

    import Cocoa
    import objc
    import Quartz
    from objc import super  # noqa: A004

    # Global variables
    frequency = [0.0, 0.0, 0.0, 0.0]
    startPoint = Quartz.CGPoint(0.0, 0.0)
    startRadius = 0.0
    startExtend = False
    endPoint = Quartz.CGPoint(0.0, 0.0)
    endRadius = 0.0
    endExtend = False

    shading = None
    function = None
    getFunction = None
    getShading = None
    colorspace = None

    DEFAULT_WIDTH = 256
    DEFAULT_HEIGHT = 256

    MAX_WIDTH = 1000
    MAX_HEIGHT = 1000

    def randomPoint():
        return Quartz.CGPoint(random.random(), random.random())

    def evaluate1(components, input_value, output_value):
        out = []
        for k in range(components - 1):
            out.append(1 + (math.sin(input[0] * frequency[k])) / 2)
        out.append(1)
        return out

    def getFunction1(colorspace):
        if sys.maxsize > 2**32:
            a_type = "d"
        else:
            a_type = "f"
        domain = array.array(a_type, [-2 * math.pi, 2 * math.pi])
        function_range = array.array(a_type, [0, 1, 0, 1, 0, 1, 0, 1, 0, 1])
        components = 1 + Quartz.CGColorSpaceGetNumberOfComponents(colorspace)

        return Quartz.CGFunctionCreate(
            components, 1, domain, components, function_range, evaluate1
        )

    def evaluate2(components, input_value, output_value):
        c = [0.510, 0.188, 0.910, 0.122]

        v = input_value[0]
        out = []
        for k in range(components - 1):
            if v < 0.5:
                out.append(c[k] * 2 * (0.5 - v))
            else:
                out.append(c[k] * 2 * (v - 0.5))
        out.append(1)
        return out

    def getFunction2(colorspace):
        domain = [0, 1]
        function_range = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1]

        components = 1 + Quartz.CGColorSpaceGetNumberOfComponents(colorspace)
        return Quartz.CGFunctionCreate(
            components, 1, domain, components, function_range, evaluate2
        )

    def evaluate3(components, input_value, output_value):
        c = [0.3, 0, 0, 0]

        out = []
        for k in range(components - 1):
            out.append(c[k] * input_value[0])
        out.append(1)
        return out

    def getFunction3(colorspace):
        domain = [0, 1]
        function_range = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1]

        components = 1 + Quartz.CGColorSpaceGetNumberOfComponents(colorspace)
        return Quartz.CGFunctionCreate(
            components, 1, domain, components, function_range, evaluate3
        )

    def getAxialShading(colorspace, function):
        return Quartz.CGShadingCreateAxial(
            colorspace, startPoint, endPoint, function, startExtend, endExtend
        )

    def getRadialShading(colorspace, function):
        return Quartz.CGShadingCreateRadial(
            colorspace,
            startPoint,
            startRadius,
            endPoint,
            endRadius,
            function,
            startExtend,
            endExtend,
        )

    class MyQuartzView(Cocoa.NSView):
        def initWithFrame_(self, frameRect):
            global startPoint, startRadius, startExtend
            global endPoint, endRadius, endExtend

            super().initWithFrame_(frameRect)

            startPoint = Quartz.CGPoint(0, 0)
            startRadius = 0
            startExtend = False

            endPoint = Quartz.CGPointMake(0, 0)
            endRadius = 0
            endExtend = False

            return self

        def drawRect_(self, rect):
            currentContext = Cocoa.NSGraphicsContext.currentContext().graphicsPort()

            # Note that at this point the current context CTM is set up such
            # that the context size corresponds to the size of the view
            # i.e. one unit in the context == one pixel
            # Also, the origin is in the bottom left of the view with +y pointing up
            global getFunction

            bounds = self.bounds()

            angle = 0
            sx = sy = 1
            width = bounds.size.width
            height = bounds.size.height

            if getFunction is None:
                self.randomize_(self)

            m = Quartz.CGAffineTransformIdentity
            m = Quartz.CGAffineTransformRotate(m, angle)
            m = Quartz.CGAffineTransformScale(m, width, height)
            m = Quartz.CGAffineTransformScale(m, sx, sy)

            Quartz.CGContextBeginPage(currentContext, bounds)

            Quartz.CGContextTranslateCTM(
                currentContext, bounds.size.width / 2, bounds.size.height / 2
            )
            Quartz.CGContextConcatCTM(currentContext, m)
            Quartz.CGContextTranslateCTM(currentContext, -0.5, -0.5)

            Quartz.CGContextSaveGState(currentContext)

            Quartz.CGContextClipToRect(currentContext, Quartz.CGRectMake(0, 0, 1, 1))
            Quartz.CGContextSetRGBFillColor(currentContext, 0.7, 0.7, 0.9, 1)
            Quartz.CGContextFillRect(currentContext, Quartz.CGRectMake(0, 0, 1, 1))

            Quartz.CGContextDrawShading(currentContext, shading)

            Quartz.CGContextRestoreGState(currentContext)

            Quartz.CGContextSaveGState(currentContext)
            Quartz.CGContextClipToRect(currentContext, Quartz.CGRectMake(0, 0, 1, 1))
            Quartz.CGContextSetRGBStrokeColor(currentContext, 1, 0, 0, 1)

            if getShading == getRadialShading:
                Quartz.CGContextAddArc(
                    currentContext,
                    startPoint.x,
                    startPoint.y,
                    startRadius,
                    math.radians(0),
                    math.radians(360),
                    True,
                )
                Quartz.CGContextClosePath(currentContext)
                Quartz.CGContextMoveToPoint(
                    currentContext, endPoint.x + endRadius, endPoint.y
                )
                Quartz.CGContextAddArc(
                    currentContext,
                    endPoint.x,
                    endPoint.y,
                    endRadius,
                    math.radians(0),
                    math.radians(360),
                    True,
                )
                Quartz.CGContextClosePath(currentContext)

            Quartz.CGContextMoveToPoint(currentContext, startPoint.x + 0.01, startPoint.y)
            Quartz.CGContextAddArc(
                currentContext,
                startPoint.x,
                startPoint.y,
                0.01,
                math.radians(0),
                math.radians(360),
                True,
            )
            Quartz.CGContextClosePath(currentContext)
            Quartz.CGContextMoveToPoint(currentContext, startPoint.x, startPoint.y)
            Quartz.CGContextAddLineToPoint(currentContext, endPoint.x, endPoint.y)

            ctm = Quartz.CGContextGetCTM(currentContext)
            Quartz.CGContextConcatCTM(currentContext, Quartz.CGAffineTransformInvert(ctm))
            Quartz.CGContextStrokePath(currentContext)
            Quartz.CGContextRestoreGState(currentContext)

            Quartz.CGContextSaveGState(currentContext)
            Quartz.CGContextSetGrayStrokeColor(currentContext, 0, 1)
            Quartz.CGContextAddRect(currentContext, Quartz.CGRectMake(0, 0, 1, 1))
            ctm = Quartz.CGContextGetCTM(currentContext)
            Quartz.CGContextConcatCTM(currentContext, Quartz.CGAffineTransformInvert(ctm))
            Quartz.CGContextStrokePath(currentContext)
            Quartz.CGContextRestoreGState(currentContext)

            Quartz.CGContextEndPage(currentContext)

            Quartz.CGContextFlush(currentContext)

        @objc.IBAction
        def randomize_(self, sender):
            global colorspace, getFunction, getShading
            global function, shading
            global startPoint, startRadius, endPoint, endRadius

            if colorspace is None:
                colorspace = Quartz.CGColorSpaceCreateDeviceRGB()

            for k in range(len(frequency)):
                frequency[k] = random.random()

            startPoint = randomPoint()
            startRadius = random.random() / 2
            endPoint = randomPoint()
            endRadius = random.random() / 2

            if getFunction == getFunction1:
                getFunction = getFunction2

            elif getFunction == getFunction2:
                getFunction = getFunction3

            else:
                getFunction = getFunction1

            if getShading == getAxialShading:
                getShading = getRadialShading
            else:
                getShading = getAxialShading

            function = getFunction(colorspace)
            shading = getShading(colorspace, function)

            self.setNeedsDisplay_(True)

        @objc.IBAction
        def toggleStartExtend_(self, sender):
            global startExtend, shading

            startExtend = not startExtend
            shading = getShading(colorspace, function)

            self.setNeedsDisplay_(True)

        @objc.IBAction
        def toggleEndExtend_(self, sender):
            global endExtend, shading

            endExtend = not endExtend
            shading = getShading(colorspace, function)

            self.setNeedsDisplay_(True)

### main.py

    import sys

    # AppHelper.runEventLoop()
    import AppKit  # noqa: F401
    import MyQuartzView  # noqa: F401

    AppKit.NSApplicationMain(sys.argv)

### setup.py

    """
    Script for building the example.

    Usage:
        python3 setup.py py2app
    """

    from setuptools import setup

    setup(
        name="CGShadingDemo",
        app=["main.py"],
        data_files=["English.lproj"],
        setup_requires=["py2app", "pyobjc-framework-Cocoa", "pyobjc-framework-Quartz"],
    )