CIMicroPaint¶
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",
],
)