# CIMicroPaint

- `Download example`

A PyObjC Example without documentation

## Sources

### CIMicroPaintView.py

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

    class CIMicroPaintView(SampleCIView):
        imageAccumulator = objc.ivar()
        brushFilter = objc.ivar()
        compositeFilter = objc.ivar()
        color = objc.ivar()
        brushSize = objc.ivar(objc._C_FLT)

        def initWithFrame_(self, frame):
            self = super().initWithFrame_(frame)
            if self is None:
                return None

            self.brushSize = 25.0
            self.color = Cocoa.NSColor.colorWithDeviceRed_green_blue_alpha_(
                0.0, 0.0, 0.0, 1.0
            )

            self.brushFilter = Quartz.CIFilter.filterWithName_("CIRadialGradient")
            self.brushFilter.setDefaults()
            for k, v in (
                (
                    "inputColor1",
                    Quartz.CIColor.colorWithRed_green_blue_alpha_(0.0, 0.0, 0.0, 0.0),
                ),
                ("inputRadius0", 0.0),
            ):
                self.brushFilter.setValue_forKey_(v, k)

            self.compositeFilter = Quartz.CIFilter.filterWithName_(
                "CISourceOverCompositing"
            )
            self.compositeFilter.setDefaults()

            return self

        def viewBoundsDidChange_(self, bounds):
            if (
                self.imageAccumulator is not None
                and bounds == self.imageAccumulator.extent()
            ):
                print("Nothing changed")
                return

            # Create a new accumulator and composite the old one over the it.

            c = Quartz.CIImageAccumulator.alloc().initWithExtent_format_(
                bounds, Quartz.kCIFormatRGBA16
            )
            f = Quartz.CIFilter.filterWithName_("CIConstantColorGenerator")
            f.setDefaults()
            f.setValue_forKey_(
                Quartz.CIColor.colorWithRed_green_blue_alpha_(1.0, 1.0, 1.0, 1.0),
                "inputColor",
            )

            if self.imageAccumulator is not None:
                f = Quartz.CIFilter.filterWithName_("CISourceOverCompositing")
                f.setDefaults()
                f.setValue_forKey_(self.imageAccumulator.image(), "inputImage")
                f.setValue_forKey_(c.image(), "inputBackgroundImage")
                c.setImage_(f.valueForKey_("outputImage"))

            self.imageAccumulator = c
            self.setImage_(self.imageAccumulator.image())

        def mouseDragged_(self, event):
            loc = self.convertPoint_fromView_(event.locationInWindow(), None)

            rect = Quartz.CGRectMake(
                loc.x - self.brushSize,
                loc.y - self.brushSize,
                2.0 * self.brushSize,
                2.0 * self.brushSize,
            )
            self.brushFilter.setValue_forKey_(self.brushSize, "inputRadius1")

            cicolor = Quartz.CIColor.alloc().initWithColor_(self.color)
            self.brushFilter.setValue_forKey_(cicolor, "inputColor0")

            self.brushFilter.setValue_forKey_(
                Quartz.CIVector.vectorWithX_Y_(loc.x, loc.y), "inputCenter"
            )

            self.compositeFilter.setValue_forKey_(
                self.brushFilter.valueForKey_("outputImage"), "inputImage"
            )
            self.compositeFilter.setValue_forKey_(
                self.imageAccumulator.image(), "inputBackgroundImage"
            )

            self.imageAccumulator.setImage_dirtyRect_(
                self.compositeFilter.valueForKey_("outputImage"), rect
            )

            self.setImage_dirtyRect_(self.imageAccumulator.image(), rect)

        def mouseDown_(self, event):
            self.mouseDragged_(event)

### SampleCIView.py

    """
    SampleCIView - simple OpenGL based CoreImage view
    """

    # XXX: FIXME
    # 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, Quartz.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 = Quartz.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 = Quartz.CGRectIntersection(
                    Quartz.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,
                        )

### main.py

    import CIMicroPaintView  # noqa: F401
    import SampleCIView  # noqa: F401
    from PyObjCTools import AppHelper

    AppHelper.runEventLoop()

### setup.py

    """
    Script for building the example.

    Usage:
        python3 setup.py py2app
    """

    from setuptools import setup

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