1from Quartz import * 2from LaunchServices import * # UT info 3import math 4 5class ImageInfo (object): 6 __slots__ = ('fRotation', 'fScaleX', 'fScaleY', 'fTranslateX', 'fTranslateY', 'fImageRef', 'fProperties', 'fOrientation') 7 8 def __init__(self): 9 self.fRotation = 0.0 # The rotation about the center of the image (degrees) 10 self.fScaleX = 0.0 # The scaling of the image along it's X-axis 11 self.fScaleY = 0.0 # The scaling of the image along it's Y-axis 12 self.fTranslateX = 0.0 # Move the image along the X-axis 13 self.fTranslateY = 0.0 # Move the image along the Y-axis 14 self.fImageRef = None # The image itself 15 self.fProperties = None # Image properties 16 self.fOrientation = None # Affine transform that ensures the image displays correctly 17 18# Create a new image from a file at the given url 19# Returns None if unsuccessful. 20def IICreateImage(url): 21 ii = None 22 # Try to create an image source to the image passed to us 23 imageSrc = CGImageSourceCreateWithURL(url, None) 24 if imageSrc is not None: 25 # And if we can, try to obtain the first image available 26 image = CGImageSourceCreateImageAtIndex(imageSrc, 0, None) 27 if image is not None: 28 # and if we could, create the ImageInfo struct with default values 29 ii = ImageInfo() 30 ii.fRotation = 0.0 31 ii.fScaleX = 1.0 32 ii.fScaleY = 1.0 33 ii.fTranslateX = 0.0 34 ii.fTranslateY = 0.0 35 # the ImageInfo struct owns this CGImageRef now, so no need for a retain. 36 ii.fImageRef = image 37 # the ImageInfo struct owns this CFDictionaryRef, so no need for a retain. 38 ii.fProperties = CGImageSourceCopyPropertiesAtIndex(imageSrc, 0, None); 39 # Setup the orientation transformation matrix so that the image will display with the 40 # proper orientation 41 IIGetOrientationTransform(ii) 42 43 return ii 44 45# Transforms the context based on the orientation of the image. 46# This ensures the image always appears correctly when drawn. 47def IIGetOrientationTransform(image): 48 w = CGImageGetWidth(image.fImageRef) 49 h = CGImageGetHeight(image.fImageRef) 50 if image.fProperties is not None: 51 # The Orientations listed here are mirroed from CGImageProperties.h, 52 # listed under the kCGImagePropertyOrientation key. 53 orientation = IIGetImageOrientation(image) 54 if orientation == 1: 55 # 1 = 0th row is at the top, and 0th column is on the left. 56 # Orientation Normal 57 image.fOrientation = CGAffineTransformMake(1.0, 0.0, 0.0, 1.0, 0.0, 0.0); 58 59 elif orientation == 2: 60 # 2 = 0th row is at the top, and 0th column is on the right. 61 # Flip Horizontal 62 image.fOrientation = CGAffineTransformMake(-1.0, 0.0, 0.0, 1.0, w, 0.0) 63 64 elif orientation == 3: 65 # 3 = 0th row is at the bottom, and 0th column is on the right. 66 # Rotate 180 degrees 67 image.fOrientation = CGAffineTransformMake(-1.0, 0.0, 0.0, -1.0, w, h) 68 69 elif orientation == 4: 70 # 4 = 0th row is at the bottom, and 0th column is on the left. 71 # Flip Vertical 72 image.fOrientation = CGAffineTransformMake(1.0, 0.0, 0, -1.0, 0.0, h) 73 74 elif orientation == 5: 75 # 5 = 0th row is on the left, and 0th column is the top. 76 # Rotate -90 degrees and Flip Vertical 77 image.fOrientation = CGAffineTransformMake(0.0, -1.0, -1.0, 0.0, h, w) 78 79 elif orientation == 6: 80 # 6 = 0th row is on the right, and 0th column is the top. 81 # Rotate 90 degrees 82 image.fOrientation = CGAffineTransformMake(0.0, -1.0, 1.0, 0.0, 0.0, w) 83 84 elif orientation == 7: 85 # 7 = 0th row is on the right, and 0th column is the bottom. 86 # Rotate 90 degrees and Flip Vertical 87 image.fOrientation = CGAffineTransformMake(0.0, 1.0, 1.0, 0.0, 0.0, 0.0) 88 89 elif orientation == 8: 90 # 8 = 0th row is on the left, and 0th column is the bottom. 91 # Rotate -90 degrees 92 image.fOrientation = CGAffineTransformMake(0.0, 1.0,-1.0, 0.0, h, 0.0) 93 94# Gets the orientation of the image from the properties dictionary if available 95# If the kCGImagePropertyOrientation is not available or invalid, 96# then 1, the default orientation, is returned. 97def IIGetImageOrientation(image): 98 result = 1 99 if image.fProperties is not None: 100 orientation = image.fProperties.get(kCGImagePropertyOrientation) 101 if orientation is not None: 102 result = orientation 103 104 return result 105 106# Save the given image to a file at the given url. 107# Returns true if successful, false otherwise. 108def IISaveImage(image, url, width, height): 109 result = False 110 111 # If there is no image, no destination, or the width/height is 0, then fail early. 112 assert (image is not None) and (url is not None) and (width != 0.0) and (height != 0.0) 113 114 # Try to create a jpeg image destination at the url given to us 115 imageDest = CGImageDestinationCreateWithURL(url, kUTTypeJPEG, 1, None) 116 if imageDest is not None: 117 # And if we can, then we can start building our final image. 118 # We begin by creating a CGBitmapContext to host our desintation image. 119 120 # Allocate enough space to hold our pixels 121 imageData = objc.allocateBuffer(int(4 * width * height)) 122 123 # Create the bitmap context 124 bitmapContext = CGBitmapContextCreate( 125 imageData, # image data we just allocated... 126 width, # width 127 height, # height 128 8, # 8 bits per component 129 4 * width, # bytes per pixel times number of pixels wide 130 CGImageGetColorSpace(image.fImageRef), # use the same colorspace as the original image 131 kCGImageAlphaPremultipliedFirst) # use premultiplied alpha 132 133 # Check that all that went well 134 if bitmapContext is not None: 135 # Now, we draw the image to the bitmap context 136 IIDrawImageTransformed(image, bitmapContext, CGRectMake(0.0, 0.0, width, height)) 137 138 # We have now gotten our image data to the bitmap context, and correspondingly 139 # into imageData. If we wanted to, we could look at any of the pixels of the image 140 # and manipulate them in any way that we desire, but for this case, we're just 141 # going to ask ImageIO to write this out to disk. 142 143 # Obtain a CGImageRef from the bitmap context for ImageIO 144 imageIOImage = CGBitmapContextCreateImage(bitmapContext) 145 146 # Check if we have additional properties from the original image 147 if image.fProperties is not None: 148 # If we do, then we want to inspect the orientation property. 149 # If it exists and is not the default orientation, then we 150 # want to replace that orientation in the destination file 151 orientation = IIGetImageOrientation(image) 152 if orientation != 1: 153 # If the orientation in the original image was not the default, 154 # then we need to replace that key in a duplicate of that dictionary 155 # and then pass that dictionary to ImageIO when adding the image. 156 prop = CFDictionaryCreateMutableCopy(None, 0, image.fProperties) 157 orientation = 1; 158 prop[kCGImagePropertyOrientation] = orientation 159 160 # And add the image with the new properties 161 CGImageDestinationAddImage(imageDest, imageIOImage, prop); 162 163 else: 164 # Otherwise, the image was already in the default orientation and we can 165 # just save it with the original properties. 166 CGImageDestinationAddImage(imageDest, imageIOImage, image.fProperties) 167 168 else: 169 # If we don't, then just add the image without properties 170 CGImageDestinationAddImage(imageDest, imageIOImage, None) 171 172 del bitmapContext 173 174 # Finalize the image destination 175 result = CGImageDestinationFinalize(imageDest) 176 del imageDest 177 178 return result 179 180 181# Applies the transformations specified in the ImageInfo struct without drawing the actual image 182def IIApplyTransformation(image, context, bounds): 183 if image is not None: 184 # Whenever you do multiple CTM changes, you have to be very careful with order. 185 # Changing the order of your CTM changes changes the outcome of the drawing operation. 186 # For example, if you scale a context by 2.0 along the x-axis, and then translate 187 # the context by 10.0 along the x-axis, then you will see your drawing will be 188 # in a different position than if you had done the operations in the opposite order. 189 190 # Our intent with this operation is that we want to change the location from which we start drawing 191 # (translation), then rotate our axies so that our image appears at an angle (rotation), and finally 192 # scale our axies so that our image has a different size (scale). 193 # Changing the order of operations will markedly change the results. 194 IITranslateContext(image, context) 195 IIRotateContext(image, context, bounds) 196 IIScaleContext(image, context, bounds) 197 198# Draw the image to the given context centered inside the given bounds 199def IIDrawImage(image, context, bounds): 200 imageRect = NSRect() 201 if image is not None and context is not None: 202 # Setup the image rect so that the image fills it's natural boudaries in the base coordinate system. 203 imageRect.origin.x = 0.0 204 imageRect.origin.y = 0.0 205 imageRect.size.width = CGImageGetWidth(image.fImageRef); 206 imageRect.size.height = CGImageGetHeight(image.fImageRef); 207 208 # Obtain the orientation matrix for this image 209 ctm = image.fOrientation; 210 211 # Before we can apply the orientation matrix, we need to translate the coordinate system 212 # so the center of the rectangle matces the center of the image. 213 if image.fProperties is None or IIGetImageOrientation(image) < 5: 214 # For orientations 1-4, the images are unrotated, so the width and height of the base image 215 # can be used as the width and height of the coordinate translation calculation. 216 CGContextTranslateCTM( 217 context, 218 math.floor((bounds.size.width - imageRect.size.width) / 2.0), 219 math.floor((bounds.size.height - imageRect.size.height) / 2.0)) 220 221 else: 222 # For orientations 5-8, the images are rotated 90 or -90 degrees, so we need to use 223 # the image width in place of the height and vice versa. 224 CGContextTranslateCTM( 225 context, 226 floorf((bounds.size.width - imageRect.size.height) / 2.0), 227 floorf((bounds.size.height - imageRect.size.width) / 2.0)) 228 229 # Finally, orient the context so that the image draws naturally. 230 CGContextConcatCTM(context, ctm) 231 232 # And draw the image. 233 CGContextDrawImage(context, imageRect, image.fImageRef) 234 235# Rotates the context around the center point of the given bounds 236def IIRotateContext(image, context, bounds): 237 # First we translate the context such that the 0,0 location is at the center of the bounds 238 CGContextTranslateCTM(context, bounds.size.width/2.0, bounds.size.height/2.0) 239 240 # Then we rotate the context, converting our angle from degrees to radians 241 CGContextRotateCTM(context, image.fRotation * math.pi / 180.0) 242 243 # Finally we have to restore the center position 244 CGContextTranslateCTM(context, -bounds.size.width/2.0, -bounds.size.height/2.0) 245 246# Scale the context around the center point of the given bounds 247def IIScaleContext(image, context, bounds): 248 # First we translate the context such that the 0,0 location is at the center of the bounds 249 CGContextTranslateCTM(context, bounds.size.width/2.0, bounds.size.height/2.0) 250 251 # Next we scale the context to the size that we want 252 CGContextScaleCTM(context, image.fScaleX, image.fScaleY); 253 254 # Finally we have to restore the center position 255 CGContextTranslateCTM(context, -bounds.size.width/2.0, -bounds.size.height/2.0) 256 257# Translate the context 258def IITranslateContext(image, context): 259 # Translation is easy, just translate. 260 CGContextTranslateCTM(context, image.fTranslateX, image.fTranslateY) 261 262# Draw the image to the given context centered inside the given bounds with 263# the transformation info. The CTM of the context is unchanged after this call 264def IIDrawImageTransformed(image, context, bounds): 265 # We save the current graphics state so as to not disrupt it for the caller. 266 CGContextSaveGState(context) 267 268 # Apply the transformation 269 IIApplyTransformation(image, context, bounds) 270 271 # Draw the image centered in the context 272 IIDrawImage(image, context, bounds) 273 274 # Restore our original graphics state. 275 CGContextRestoreGState(context) 276 277# Release the ImageInfo struct and other associated data 278# you should not refer to the reference after this call 279# This function is None safe. 280def IIRelease(image): 281 pass 282