CGRotation is a Cocoa application that demonstrates how to use the Current Transformation Matrix (CTM) to apply rotation, scaling and transformation to an image for display. It also demonstrates simple usage of ImageIO to load and save images and how to get the pixel data from a CGImageRef into an arbitrary buffer.


import objc
import Quartz
import Cocoa
import LaunchServices
import math

class ImageInfo (object):
    __slots__ = ('fRotation', 'fScaleX', 'fScaleY', 'fTranslateX', 'fTranslateY', 'fImageRef', 'fProperties', 'fOrientation')

    def __init__(self):
        self.fRotation = 0.0        # The rotation about the center of the image (degrees)
        self.fScaleX = 0.0          # The scaling of the image along it's X-axis
        self.fScaleY = 0.0          # The scaling of the image along it's Y-axis
        self.fTranslateX = 0.0      # Move the image along the X-axis
        self.fTranslateY = 0.0      # Move the image along the Y-axis
        self.fImageRef = None       # The image itself
        self.fProperties = None     # Image properties
        self.fOrientation = None    # Affine transform that ensures the image displays correctly

# Create a new image from a file at the given url
# Returns None if unsuccessful.
def IICreateImage(url):
    ii = None
    # Try to create an image source to the image passed to us
    imageSrc = Quartz.CGImageSourceCreateWithURL(url, None)
    if imageSrc is not None:
        # And if we can, try to obtain the first image available
        image = Quartz.CGImageSourceCreateImageAtIndex(imageSrc, 0, None)
        if image is not None:
            # and if we could, create the ImageInfo struct with default values
            ii = ImageInfo()
            ii.fRotation = 0.0
            ii.fScaleX = 1.0
            ii.fScaleY = 1.0
            ii.fTranslateX = 0.0
            ii.fTranslateY = 0.0
            # the ImageInfo struct owns this CGImageRef now, so no need for a retain.
            ii.fImageRef = image
            # the ImageInfo struct owns this CFDictionaryRef, so no need for a retain.
            ii.fProperties = Quartz.CGImageSourceCopyPropertiesAtIndex(imageSrc, 0, None);
            # Setup the orientation transformation matrix so that the image will display with the
            # proper orientation

    return ii

# Transforms the context based on the orientation of the image.
# This ensures the image always appears correctly when drawn.
def IIGetOrientationTransform(image):
    w = Quartz.CGImageGetWidth(image.fImageRef)
    h = Quartz.CGImageGetHeight(image.fImageRef)
    if image.fProperties is not None:
        # The Orientations listed here are mirroed from CGImageProperties.h,
        # listed under the kCGImagePropertyOrientation key.
        orientation = IIGetImageOrientation(image)
        if orientation == 1:
            # 1 = 0th row is at the top, and 0th column is on the left.
            # Orientation Normal
            image.fOrientation = Quartz.CGAffineTransformMake(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);

        elif orientation == 2:
            # 2 = 0th row is at the top, and 0th column is on the right.
            # Flip Horizontal
            image.fOrientation = Quartz.CGAffineTransformMake(-1.0, 0.0, 0.0, 1.0, w, 0.0)

        elif orientation == 3:
            # 3 = 0th row is at the bottom, and 0th column is on the right.
            # Rotate 180 degrees
            image.fOrientation = Quartz.CGAffineTransformMake(-1.0, 0.0, 0.0, -1.0, w, h)

        elif orientation == 4:
            # 4 = 0th row is at the bottom, and 0th column is on the left.
            # Flip Vertical
            image.fOrientation = Quartz.CGAffineTransformMake(1.0, 0.0, 0, -1.0, 0.0, h)

        elif orientation == 5:
            # 5 = 0th row is on the left, and 0th column is the top.
            # Rotate -90 degrees and Flip Vertical
            image.fOrientation = Quartz.CGAffineTransformMake(0.0, -1.0, -1.0, 0.0, h, w)

        elif orientation == 6:
            # 6 = 0th row is on the right, and 0th column is the top.
            # Rotate 90 degrees
            image.fOrientation = Quartz.CGAffineTransformMake(0.0, -1.0, 1.0, 0.0, 0.0, w)

        elif orientation == 7:
            # 7 = 0th row is on the right, and 0th column is the bottom.
            # Rotate 90 degrees and Flip Vertical
            image.fOrientation = Quartz.CGAffineTransformMake(0.0, 1.0, 1.0, 0.0, 0.0, 0.0)

        elif orientation == 8:
            # 8 = 0th row is on the left, and 0th column is the bottom.
            # Rotate -90 degrees
            image.fOrientation = Quartz.CGAffineTransformMake(0.0, 1.0,-1.0, 0.0, h, 0.0)

# Gets the orientation of the image from the properties dictionary if available
# If the kCGImagePropertyOrientation is not available or invalid,
# then 1, the default orientation, is returned.
def IIGetImageOrientation(image):
    result = 1
    if image.fProperties is not None:
        orientation = image.fProperties.get(Quartz.kCGImagePropertyOrientation)
        if orientation is not None:
            result = orientation

    return result

# Save the given image to a file at the given url.
# Returns true if successful, false otherwise.
def IISaveImage(image, url, width, height):
    result = False

    # If there is no image, no destination, or the width/height is 0, then fail early.
    assert (image is not None) and (url is not None) and (width != 0.0) and (height != 0.0)

    # Try to create a jpeg image destination at the url given to us
    imageDest = Quartz.CGImageDestinationCreateWithURL(url, LaunchServices.kUTTypeJPEG, 1, None)
    if imageDest is not None:
        # And if we can, then we can start building our final image.
        # We begin by creating a CGBitmapContext to host our desintation image.

        # Allocate enough space to hold our pixels
        imageData = objc.allocateBuffer(int(4 * width * height))

        # Create the bitmap context
        bitmapContext = Quartz.CGBitmapContextCreate(
                imageData, # image data we just allocated...
                width, # width
                height, # height
                8, # 8 bits per component
                4 * width, # bytes per pixel times number of pixels wide
                Quartz.CGImageGetColorSpace(image.fImageRef), # use the same colorspace as the original image
                Quartz.kCGImageAlphaPremultipliedFirst) # use premultiplied alpha

        # Check that all that went well
        if bitmapContext is not None:
            # Now, we draw the image to the bitmap context
            IIDrawImageTransformed(image, bitmapContext, Quartz.CGRectMake(0.0, 0.0, width, height))

            # We have now gotten our image data to the bitmap context, and correspondingly
            # into imageData. If we wanted to, we could look at any of the pixels of the image
            # and manipulate them in any way that we desire, but for this case, we're just
            # going to ask ImageIO to write this out to disk.

            # Obtain a CGImageRef from the bitmap context for ImageIO
            imageIOImage = Quartz.CGBitmapContextCreateImage(bitmapContext)

            # Check if we have additional properties from the original image
            if image.fProperties is not None:
                # If we do, then we want to inspect the orientation property.
                # If it exists and is not the default orientation, then we
                # want to replace that orientation in the destination file
                orientation = IIGetImageOrientation(image)
                if orientation != 1:
                    # If the orientation in the original image was not the default,
                    # then we need to replace that key in a duplicate of that dictionary
                    # and then pass that dictionary to ImageIO when adding the image.
                    prop = CFDictionaryCreateMutableCopy(None, 0, image.fProperties)
                    orientation = 1;
                    prop[Quartz.kCGImagePropertyOrientation] = orientation

                    # And add the image with the new properties
                    Quartz.CGImageDestinationAddImage(imageDest, imageIOImage, prop);

                    # Otherwise, the image was already in the default orientation and we can
                    # just save it with the original properties.
                    Quartz.CGImageDestinationAddImage(imageDest, imageIOImage, image.fProperties)

                # If we don't, then just add the image without properties
                Quartz.CGImageDestinationAddImage(imageDest, imageIOImage, None)

            del bitmapContext

        # Finalize the image destination
        result = Quartz.CGImageDestinationFinalize(imageDest)
        del imageDest

    return result

# Applies the transformations specified in the ImageInfo struct without drawing the actual image
def IIApplyTransformation(image, context, bounds):
    if image is not None:
        # Whenever you do multiple CTM changes, you have to be very careful with order.
        # Changing the order of your CTM changes changes the outcome of the drawing operation.
        # For example, if you scale a context by 2.0 along the x-axis, and then translate
        # the context by 10.0 along the x-axis, then you will see your drawing will be
        # in a different position than if you had done the operations in the opposite order.

        # Our intent with this operation is that we want to change the location from which we start drawing
        # (translation), then rotate our axies so that our image appears at an angle (rotation), and finally
        # scale our axies so that our image has a different size (scale).
        # Changing the order of operations will markedly change the results.
        IITranslateContext(image, context)
        IIRotateContext(image, context, bounds)
        IIScaleContext(image, context, bounds)

# Draw the image to the given context centered inside the given bounds
def IIDrawImage(image, context, bounds):
    imageRect = Cocoa.NSRect()
    if image is not None and context is not None:
        # Setup the image rect so that the image fills it's natural boudaries in the base coordinate system.
        imageRect.origin.x = 0.0
        imageRect.origin.y = 0.0
        imageRect.size.width = Quartz.CGImageGetWidth(image.fImageRef);
        imageRect.size.height = Quartz.CGImageGetHeight(image.fImageRef);

        # Obtain the orientation matrix for this image
        ctm = image.fOrientation;

        # Before we can apply the orientation matrix, we need to translate the coordinate system
        # so the center of the rectangle matces the center of the image.
        if image.fProperties is None or IIGetImageOrientation(image) < 5:
            # For orientations 1-4, the images are unrotated, so the width and height of the base image
            # can be used as the width and height of the coordinate translation calculation.
                math.floor((bounds.size.width - imageRect.size.width) / 2.0),
                math.floor((bounds.size.height - imageRect.size.height) / 2.0))

            # For orientations 5-8, the images are rotated 90 or -90 degrees, so we need to use
            # the image width in place of the height and vice versa.
                floorf((bounds.size.width - imageRect.size.height) / 2.0),
                floorf((bounds.size.height - imageRect.size.width) / 2.0))

        # Finally, orient the context so that the image draws naturally.
        Quartz.CGContextConcatCTM(context, ctm)

        # And draw the image.
        Quartz.CGContextDrawImage(context, imageRect, image.fImageRef)

# Rotates the context around the center point of the given bounds
def IIRotateContext(image, context, bounds):
    # First we translate the context such that the 0,0 location is at the center of the bounds
    Quartz.CGContextTranslateCTM(context, bounds.size.width/2.0, bounds.size.height/2.0)

    # Then we rotate the context, converting our angle from degrees to radians
    Quartz.CGContextRotateCTM(context, image.fRotation * math.pi / 180.0)

    # Finally we have to restore the center position
    Quartz.CGContextTranslateCTM(context, -bounds.size.width/2.0, -bounds.size.height/2.0)

# Scale the context around the center point of the given bounds
def IIScaleContext(image, context, bounds):
    # First we translate the context such that the 0,0 location is at the center of the bounds
    Quartz.CGContextTranslateCTM(context, bounds.size.width/2.0, bounds.size.height/2.0)

    # Next we scale the context to the size that we want
    Quartz.CGContextScaleCTM(context, image.fScaleX, image.fScaleY);

    # Finally we have to restore the center position
    Quartz.CGContextTranslateCTM(context, -bounds.size.width/2.0, -bounds.size.height/2.0)

# Translate the context
def IITranslateContext(image, context):
    # Translation is easy, just translate.
    Quartz.CGContextTranslateCTM(context, image.fTranslateX, image.fTranslateY)

# Draw the image to the given context centered inside the given bounds with
# the transformation info. The CTM of the context is unchanged after this call
def IIDrawImageTransformed(image, context, bounds):
    # We save the current graphics state so as to not disrupt it for the caller.

    # Apply the transformation
    IIApplyTransformation(image, context, bounds)

    # Draw the image centered in the context
    IIDrawImage(image, context, bounds)

    # Restore our original graphics state.

# Release the ImageInfo struct and other associated data
# you should not refer to the reference after this call
# This function is None safe.
def IIRelease(image):

import objc
import Cocoa
import Quartz
import CGImageUtils

class CGImageView (Cocoa.NSView):
    _image = objc.ivar()

    def setImage_(self, img):
        if img is not None and self._image is not img:
            self._image = img;
            # Mark this view as needing to be redisplayed.

    def image(self):
        return self._image

    def drawRect_(self, rect):
        # Obtain the current context
        ctx = Cocoa.NSGraphicsContext.currentContext().graphicsPort()

        # Draw the image in the context
        CGImageUtils.IIDrawImageTransformed(self._image, ctx,
                Quartz.CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height))

        # Draw the view border, just a simple stroked rectangle
        Quartz.CGContextAddRect(ctx, Quartz.CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height))
        Quartz.CGContextSetRGBStrokeColor(ctx, 1.0, 0.0, 0.0, 1.0)

import objc
import Quartz
import Cocoa
import CGImageUtils
import LaunchServices
import math

class Controller (Cocoa.NSObject):
    imageView = objc.IBOutlet()
    scaleYView = objc.IBOutlet()
    textScaleYView = objc.IBOutlet()

    _rotation = objc.ivar.float()
    _scaleX = objc.ivar.float()
    _scaleY = objc.ivar.float()
    _translateX = objc.ivar.float()
    _translateY = objc.ivar.float()
    _preserveAspectRatio = objc.ivar.bool()

    openImageIOSupportedTypes = objc.ivar()

    def awakeFromNib(self):
        self.openImageIOSupportedTypes = None
        # Ask CFBundle for the location of our demo image
        url = Cocoa.CFBundleCopyResourceURL(Cocoa.CFBundleGetMainBundle(), u"demo", u"png", None)
        if url is not None:
            # And if available, load it


    def changeScaleX_(self, sender):
        self.setScaleX_(self._scaleX + sender.floatValue())

    def changeScaleY_(self, sender):
        self.setScaleY_(self._scaleY + sender.floatValue())

    def changeTranslateX_(self, sender):
        self.setTranslateX_(self._translateX + sender.floatValue())

    def changeTranslateY_(self, sender):
        self.setTranslateY_(self._translateY + sender.floatValue())

    def reset_(self, sender):


    # Returns an array with the extensions that match the given Uniform Type Identifier (UTI).
    def extensionsForUTI_(self, uti):
        # If anything goes wrong, we'll return None, otherwise this will be the array of extensions
        # for this image type.
        extensions = None
        # Only get extensions for UTIs that are images (i.e. conforms to public.image aka kUTTypeImage)
        # This excludes PDF support that ImageIO advertises, but won't actually use.
        if LaunchServices.UTTypeConformsTo(uti, LaunchServices.kUTTypeImage):
            # Copy the decleration for the UTI (if it exists)
            decleration = LaunchServices.UTTypeCopyDeclaration(uti)
            if decleration is not None:
                # Grab the tags for this UTI, which includes extensions, OSTypes and MIME types.
                tags = Cocoa.CFDictionaryGetValue(decleration, LaunchServices.kUTTypeTagSpecificationKey)
                if tags is not None:
                    # We are interested specifically in the extensions that this UTI uses
                    filenameExtensions = tags.get(LaunchServices.kUTTagClassFilenameExtension)
                    if filenameExtensions is not None:
                        # It is valid for a UTI to export either an Array (of Strings) representing
                        # multiple tags, or a String representing a single tag.
                        type = Cocoa.CFGetTypeID(filenameExtensions)
                        if type == Cocoa.CFStringGetTypeID():
                            # If a string was exported, then wrap it up in an array.
                            extensions = Cocoa.NSArray.arrayWithObject_(filenameExtensions)
                        elif type == Cocoa.CFArrayGetTypeID():
                            # If an array was exported, then just return that array.
                            extensions = filenameExtensions.copy()

        return extensions

    # On Tiger NSOpenPanel only understands extensions, not UTIs, so we have to obtain a list of extentions
    # from the UTIs that Image IO tells us it can handle.
    def createOpenTypesArray(self):
        if self.openImageIOSupportedTypes is None:
            imageIOUTIs = Quartz.CGImageSourceCopyTypeIdentifiers()
            count = len(imageIOUTIs)
            self.openImageIOSupportedTypes = Cocoa.NSMutableArray.alloc().initWithCapacity_(count)
            for i in range(count):

    def openDocument_(self, sender):
        panel = Cocoa.NSOpenPanel.openPanel()


                None, None, self.openImageIOSupportedTypes, self.imageView.window(), self,
                'openImageDidEnd:returnCode:contextInfo:', None)

    def openImageDidEnd_returnCode_contextInfo_(self, panel, returnCode, contextInfo_):
        if returnCode == Cocoa.NSOKButton:
            if len(panel.filenames()) > 0:
                image = CGImageUtils.IICreateImage(Cocoa.NSURL.fileURLWithPath_(panel.filenames()[0]))
                if image is not None:
                    # Ownership is transferred to the CGImageView.

    def saveDocumentAs_(self, sender):
        panel = Cocoa.NSSavePanel.savePanel()

                None, "untitled image", self.imageView.window(), self,
                'saveImageDidEnd:returnCode:contextInfo:', None)

    def saveImageDidEnd_returnCode_contextInfo_(self, panel, returnCode, contextInfo):
        if returnCode == Cocoa.NSOKButton:
            frame = self.imageView.frame()
            CGImageUtils.IISaveImage(self.imageView.image(), panel.URL(),
                    math.ceil(frame.size.width), math.ceil(frame.size.height))

    def setRotation_(self, r):
        r = r % 360.0
        if r < 0:
            r += 360.0

        self._rotation = r
        self.imageView.image().fRotation = 360.0 - r # XXX

    def setScaleX_(self, x):
        self._scaleX = x;
        self.imageView.image().fScaleX = self._scaleX
        if self._preserveAspectRatio:
            self.imageView.image().fScaleY = self._scaleX


    def setScaleY_(self, y):
        self._scaleY = y
        if not self._preserveAspectRatio:
            self.imageView.image().fScaleY = self._scaleY

    def setPreserveAspectRatio_(self, preserve):
        self._preserveAspectRatio = preserve
        self.imageView.image().fScaleX = self._scaleX
        if self._preserveAspectRatio:
            self.imageView.image().fScaleY = self._scaleX

            self.imageView.image().fScaleY = self._scaleY

        self.scaleYView.setEnabled_(not self._preserveAspectRatio)
        self.textScaleYView.setEnabled_(not self._preserveAspectRatio)

    def setTranslateX_(self, x):
        self._translateX = x
        self.imageView.image().fTranslateX = self._translateX

    def setTranslateY_(self, y):
        self._translateY = y
        self.imageView.image().fTranslateY = self._translateY

    def rotation(self):
        return self._rotation

    def scaleX(self):
        return self._scaleX

    def scaleY(self):
        return self._scaleY

    def preserveAspectRatio(self):
        return self._preserveAspectRatio

    def translateX(self):
        return self._translateX

    def translateY(self):
        return self._translateY

from PyObjCTools import AppHelper
import objc; objc.setVerbose(True)

import CGImageView
import Controller
import CGImageUtils


Script for building the example.

    python3 py2app
from setuptools import setup