• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /macosx-10.10/pyobjc-45/2.6/pyobjc/pyobjc-framework-Quartz/Examples/Programming with Quartz/BasicDrawing/
1import sys
2
3import objc
4
5from Quartz import *
6import Quartz
7
8from LaunchServices import * # kUTType* constants
9
10import Utilities
11import DrawingBasics
12import AppDrawing
13
14#import Carbon.QT
15
16
17BEST_BYTE_ALIGNMENT = 16
18def COMPUTE_BEST_BYTES_PER_ROW(bpr):
19    return ((int(bpr) + BEST_BYTE_ALIGNMENT-1) & ~(BEST_BYTE_ALIGNMENT-1))
20
21# We need to ensure that the raster data stays alive until we clean up
22# the context, store it here.
23_rasterDataForContext = {}
24
25def createRGBBitmapContext(width, height, wantDisplayColorSpace, needsTransparentBitmap):
26    # This routine allocates data for a pixel array that contains width*height
27    # pixels where each pixel is 4 bytes. The format is 8-bit ARGB or XRGB, depending on
28    # whether needsTransparentBitmap is true. In order to get the recommended
29    # pixel alignment, the bytesPerRow is rounded up to the nearest multiple
30    # of BEST_BYTE_ALIGNMENT bytes.
31
32    # Minimum bytes per row is 4 bytes per sample * number of samples.
33    bytesPerRow = width*4;
34    # Round to nearest multiple of BEST_BYTE_ALIGNMENT.
35    bytesPerRow = COMPUTE_BEST_BYTES_PER_ROW(bytesPerRow);
36
37    # Allocate the data for the raster. The total amount of data is bytesPerRow
38    # times the number of rows. The function 'calloc' is used so that the
39    # memory is initialized to 0.
40    try:
41        rasterData = objc.allocateBuffer(int(bytesPerRow * height))
42    except  MemoryError:
43        return None
44
45    # The wantDisplayColorSpace argument passed to the function determines
46    # whether or not to use the display color space or the generic calibrated
47    # RGB color space. The needsTransparentBitmap argument determines whether
48    # create a context that records alpha or not.
49    if wantDisplayColorSpace:
50        cs = Utilities.getTheDisplayColorSpace()
51    else:
52        cs = Utilities.getTheCalibratedRGBColorSpace()
53
54    if needsTransparentBitmap:
55        transparency = kCGImageAlphaPremultipliedFirst
56    else:
57        transparency = kCGImageAlphaPremultipliedFirst
58
59    context = CGBitmapContextCreate(rasterData, width, height, 8, bytesPerRow,
60            cs, transparency)
61    if context is None:
62        return None
63
64    _rasterDataForContext[context] = rasterData
65
66    # Either clear the rect or paint with opaque white, depending on
67    # the needs of the caller.
68    if needsTransparentBitmap:
69        # Clear the context bits so they are transparent.
70        CGContextClearRect(context, CGRectMake(0, 0, width, height))
71
72    else:
73        # Since the drawing destination is opaque, first paint
74        # the context bits to white.
75        CGContextSaveGState(context)
76        CGContextSetFillColorWithColor(context, Utilities.getRGBOpaqueWhiteColor())
77        CGContextFillRect(context, CGRectMake(0, 0, width, height))
78        CGContextRestoreGState(context)
79
80    return context
81
82def myCGContextGetBitmapInfo(c):
83    if hasattr(Quartz, 'CGBitmapContextGetBitmapInfo'):
84        return CGBitmapContextGetBitmapInfo(c)
85    else:
86        return CGBitmapContextGetAlphaInfo(c)
87
88# createImageFromBitmapContext creates a CGImageRef
89# from a bitmap context. Calling this routine
90# transfers 'ownership' of the raster data
91# in the bitmap context, to the image. If the
92# image can't be created, this routine frees
93# the memory associated with the raster.
94def createImageFromBitmapContext(c):
95    rasterData = _rasterDataForContext[c]
96    # We own the data, hence remove from the mapping
97    del _rasterDataForContext[c]
98
99    imageDataSize = CGBitmapContextGetBytesPerRow(c)*CGBitmapContextGetHeight(c)
100
101    if rasterData is None:
102        fprintf(stderr, "Context is not a bitmap context!")
103
104    # Create the data provider from the image data
105    dataProvider = CGDataProviderCreateWithData(None,
106                                rasterData,
107                                imageDataSize,
108                                None)
109    if dataProvider is None:
110        print >>sys.stderr, "Couldn't create data provider!"
111        return None
112
113    # Now create the image. The parameters for the image closely match
114    # the parameters of the bitmap context. This code uses a NULL
115    # decode array and shouldInterpolate is true.
116    image = CGImageCreate(
117                CGBitmapContextGetWidth(c),
118                CGBitmapContextGetHeight(c),
119                CGBitmapContextGetBitsPerComponent(c),
120                CGBitmapContextGetBitsPerPixel(c),
121                CGBitmapContextGetBytesPerRow(c),
122                CGBitmapContextGetColorSpace(c),
123                myCGContextGetBitmapInfo(c),
124		dataProvider,
125                None,
126                True,
127                kCGRenderingIntentDefault)
128
129    if image is None:
130        print >>sys.stderr, "Couldn't create image!"
131        return None
132    return image
133
134
135def exportCGImageToFileWithQT(image, url, outputFormat, dpi):
136    """
137    Export an image using QuickTime API's.
138
139    This function can't possibly work, as the relevant APIs aren't available
140    through MacPython. The code below is mostly there in case someone fixes
141    the MacPython QuickTime bindings.
142    """
143    return
144
145    if outputFormat.lower() == kUTTypeTIFF.lower():
146        imageExportType = kQTFileTypeTIFF;
147
148    elif outputFormat.lower() == kUTTypePNG.lower():
149        imageExportType = kQTFileTypePNG;
150
151    elif outputFormat.lower() == kUTTypeJPEG.lower():
152        imageExportType = kQTFileTypeJPEG;
153
154    else:
155        print >>sys.stderr, "Requested image export format %@s unsupported"%(outputFormat,)
156        return
157
158	result, dataRef, dataRefType = QTNewDataReferenceFromCFURL(url, 0,  None, None)
159        if result == 0:
160            result, graphicsExporter = OpenADefaultComponent(GraphicsExporterComponentType,
161				    imageExportType, graphicsExporter)
162            if result == 0:
163                result = GraphicsExportSetInputCGImage(graphicsExporter, image)
164                if result == 0:
165                    result = GraphicsExportSetResolution(graphicsExporter,
166                                            FloatToFixed(dpi), FloatToFixed(dpi));
167                if result == 0:
168                    result = GraphicsExportSetOutputDataReference(
169                                            graphicsExporter, dataRef, dataRefType);
170                if result == 0:
171                    result, sizeWritten = GraphicsExportDoExport(
172                                            graphicsExporter, None)
173                CloseComponent(graphicsExporter);
174
175        if dataRef:
176		DisposeHandle(dataRef);
177
178        if result:
179		print >>sys.stderr, "QT export got bad result = %d!"%(result,)
180
181
182def exportCGImageToFileWithDestination(image, url, outputFormat, dpi):
183    # Create an image destination at the supplied URL that
184    # corresponds to the output image format. The destination will
185    # only contain 1 image.
186    imageDestination = CGImageDestinationCreateWithURL(url, outputFormat, 1, None)
187
188    if imageDestination is None:
189        print >>sys.stderr, "Couldn't create image destination!"
190        return
191
192    # Create an options dictionary with the X&Y resolution of the image
193    options = {
194        kCGImagePropertyDPIWidth: dpi,
195        kCGImagePropertyDPIHeight: dpi,
196    }
197
198    # Add the image with the options dictionary to the destination.
199    CGImageDestinationAddImage(imageDestination, image, options);
200
201    # When all the images are added to the destination, finalize it.
202    CGImageDestinationFinalize(imageDestination);
203
204def MakeImageDocument(url, imageType, exportInfo):
205    # First make a bitmap context for a US Letter size
206    # raster at the requested resolution.
207    dpi = exportInfo.dpi;
208    width = int(8.5*dpi)
209    height = int(11*dpi)
210
211    # For JPEG output type the bitmap should not be transparent. If other types are added that
212    # do not support transparency, this code should be updated to check for those types as well.
213    needTransparentBitmap = (imageType.lower() != kUTTypeJPEG.lower())
214
215    # Create an RGB Bitmap context using the generic calibrated RGB color space
216    # instead of the display color space.
217    useDisplayColorSpace = False;
218    c = createRGBBitmapContext(width, height, useDisplayColorSpace, needTransparentBitmap)
219
220    if c is None:
221        print >>sys.stderr, "Couldn't make destination bitmap context"
222        return memFullErr;
223
224    # Scale the coordinate system based on the resolution in dots per inch.
225    CGContextScaleCTM(c, dpi/72, dpi/72);
226
227    # Set the font smoothing parameter to false since it's better to
228    # draw any text without special LCD text rendering when creating
229    # rendered data for export.
230    if hasattr(Quartz, 'CGContextSetShouldSmoothFonts'):
231        CGContextSetShouldSmoothFonts(c, False)
232
233    # Set the scaling factor for shadows. This is a hack so that
234    # drawing code that needs to know the scaling factor can
235    # obtain it. Better would be that DispatchDrawing and the code
236    # it calls would take this scaling factor as a parameter.
237    Utilities.setScalingFactor(dpi/72)
238
239    # Draw into that raster...
240    AppDrawing.DispatchDrawing(c, exportInfo.command)
241
242    # Set the scaling factor back to 1.0.
243    Utilities.setScalingFactor(1.0)
244
245    # Create an image from the raster data. Calling
246    # createImageFromBitmapContext gives up ownership
247    # of the raster data used by the context.
248    image = createImageFromBitmapContext(c);
249
250    # Release the context now that the image is created.
251    del c
252
253    if image is None:
254        # Users of this code should update this to be an error code they find useful.
255        return memFullErr
256
257    # Now export the image.
258    if exportInfo.useQTForExport:
259        exportCGImageToFileWithQT(image, url, imageType, exportInfo.dpi)
260    else:
261        exportCGImageToFileWithDestination(image, url, imageType, exportInfo.dpi)
262
263
264def MakeTIFFDocument(url, exportInfo):
265    return MakeImageDocument(url, kUTTypeTIFF, exportInfo)
266
267def MakePNGDocument(url, exportInfo):
268    return MakeImageDocument(url, kUTTypePNG, exportInfo)
269
270def MakeJPEGDocument(url, exportInfo):
271    return MakeImageDocument(url, kUTTypeJPEG, exportInfo)
272
273def createCGLayerForDrawing(c):
274    rect = CGRectMake(0, 0, 50, 50)
275
276    # Make the layer the size of the rectangle that
277    # this code draws into the layer.
278    layerSize = rect.size
279
280    # Create the layer to draw into.
281    layer = CGLayerCreateWithContext(c, layerSize, None);
282    if layer is None:
283        return None
284
285    # Get the context corresponding to the layer.
286    layerContext = CGLayerGetContext(layer)
287    if layerContext is None:
288        return None
289
290    #$ Set the fill color to opaque black.
291    CGContextSetFillColorWithColor(layerContext, Utilities.getRGBOpaqueBlackColor())
292
293    # Draw the content into the layer.
294    CGContextFillRect(layerContext, rect)
295
296    # Now the layer has the contents needed.
297    return layer
298
299def doSimpleCGLayer(context):
300    # Create the layer.
301    layer = createCGLayerForDrawing(context)
302
303    if layer is None:
304        print >>sys.stderr, "Couldn't create layer!"
305        return
306
307    # Get the size of the layer created.
308    s = CGLayerGetSize(layer)
309
310    # Clip to a rect that corresponds to
311    # a grid of 8x8 layer objects.
312    CGContextClipToRect(context, CGRectMake(0, 0, 8*s.width, 8*s.height))
313
314    # Paint 8 rows of layer objects.
315    for j in range(8):
316        CGContextSaveGState(context)
317        # Paint 4 columns of layer objects, moving
318        # across the drawing canvas by skipping a
319        # square on the grid each time across.
320        for i in range(4):
321                # Draw the layer at the current origin.
322                CGContextDrawLayerAtPoint(context,
323                        CGPointZero,
324                        layer);
325                # Translate across two layer widths.
326                CGContextTranslateCTM(context, 2*s.width, 0);
327
328        CGContextRestoreGState(context)
329        # Translate to the left one layer width on
330        # even loop counts and to the right one
331        # layer width on odd loop counts. Each
332        # time through the outer loop, translate up
333        # one layer height.
334        if j % 2:
335            CGContextTranslateCTM(context,
336                s.width, s.height)
337        else:
338            CGContextTranslateCTM(context,
339                -s.width, s.height)
340
341def createAlphaOnlyContext(width, height):
342    # This routine allocates data for a pixel array that contains
343    # width*height pixels, each pixel is 1 byte. The format is
344    # 8 bits per pixel, where the data is the alpha value of the pixel.
345
346    # Minimum bytes per row is 1 byte per sample * number of samples.
347    bytesPerRow = width;
348    # Round to nearest multiple of BEST_BYTE_ALIGNMENT.
349    bytesPerRow = COMPUTE_BEST_BYTES_PER_ROW(bytesPerRow);
350
351    # Allocate the data for the raster. The total amount of data is bytesPerRow
352    #// times the number of rows. The function 'calloc' is used so that the
353    #// memory is initialized to 0.
354    try:
355        rasterData = objc.allocateBuffer(bytesPerRow * height);
356    except MemoryError:
357        return None
358
359    # This type of context is only available in Panther and later, otherwise
360    # this fails and returns a NULL context. The color space for an alpha
361    #// only context is NULL and the BitmapInfo value is kCGImageAlphaOnly.
362    context = CGBitmapContextCreate(rasterData, width, height, 8, bytesPerRow,
363					None, kCGImageAlphaOnly);
364    if context is None:
365        print >>sys.stderr, "Couldn't create the context!"
366        return None
367
368    _rasterDataForContext[context] = rasterData
369
370    # Clear the context bits so they are initially transparent.
371    CGContextClearRect(context, CGRectMake(0, 0, width, height))
372
373    return context;
374
375# createMaskFromAlphaOnlyContext creates a CGImageRef
376# from an alpha-only bitmap context. Calling this routine
377# transfers 'ownership' of the raster data in the bitmap
378# context, to the image. If the image can't be created, this
379# routine frees the memory associated with the raster.
380
381def createMaskFromAlphaOnlyContext(alphaContext):
382    rasterData = _rasterDataForContext[alphaContext]
383    # We own the data, hence remove from the mapping
384    del _rasterDataForContext[alphaContext]
385
386    imageDataSize = CGBitmapContextGetBytesPerRow(alphaContext) * CGBitmapContextGetHeight(alphaContext)
387    invertDecode = [ 1.0, 0.0 ]
388
389    # Create the data provider from the image data.
390    dataProvider = CGDataProviderCreateWithData(None,
391					    rasterData,
392					    imageDataSize,
393					    None)
394
395    if dataProvider is None:
396        print >>sys.stderr, "Couldn't create data provider!"
397        return None
398
399    mask = CGImageMaskCreate(CGBitmapContextGetWidth(alphaContext),
400			  CGBitmapContextGetHeight(alphaContext),
401			  CGBitmapContextGetBitsPerComponent(alphaContext),
402			  CGBitmapContextGetBitsPerPixel(alphaContext),
403			  CGBitmapContextGetBytesPerRow(alphaContext),
404			  dataProvider,
405                          # The decode is an inverted decode since a mask has the opposite
406                          # sense than alpha, i.e. 0 in a mask paints 100% and 1 in a mask
407                          # paints nothing.
408			  invertDecode,
409			  True)
410
411    if mask is None:
412        print >>sys.stderr, "Couldn't create image mask!"
413        return None
414
415    return mask
416
417def doAlphaOnlyContext(context):
418    # This code is going to capture the alpha coverage
419    # of the drawing done by the doAlphaRects routine.
420    # The value passed here as the width and height is
421    # the size of the bounding rectangle of that drawing.
422    width = 520
423    height = 400
424    alphaContext = createAlphaOnlyContext(width, height);
425    if context is None:
426        print >>sys.stderr, "Couldn't create the alpha-only context!"
427        return
428
429    # Draw the content to the alpha-only context, capturing
430    # the alpha coverage. The doAlphaRects routine paints
431    # a series of translucent red rectangles.
432    DrawingBasics.doAlphaRects(alphaContext)
433
434    # Finished drawing to the context and now the raster contains
435    # the alpha data captured from the drawing. Create
436    # the mask from the data in the context.
437    mask = createMaskFromAlphaOnlyContext(alphaContext);
438    # This code is now finshed with the context so it can
439    # release it.
440    del alphaContext
441
442    if mask is None:
443        return
444
445    # Set the fill color space.
446    CGContextSetFillColorSpace(context, Utilities.getTheCalibratedRGBColorSpace());
447    opaqueBlue = ( 0.11, 0.208, 0.451, 1.0 )
448    # Set the painting color to opaque blue.
449    CGContextSetFillColor(context, opaqueBlue);
450    # Draw the mask, painting the mask with blue. This colorizes
451    # the image to blue and it is as if we painted the
452    # alpha rects with blue instead of red.
453    CGContextDrawImage(context, CGRectMake(0, 0, width, height), mask);
454
455_pdfDoc = None
456_pdfURL = None
457_width = 0
458_height = 0
459def getThePDFDoc(url):
460    """
461    This function caches a CGPDFDocumentRef for
462    the most recently requested PDF document.
463    """
464    global _pdfDoc, _pdfURL, _width, _height
465
466    if url is None:
467        return None, 0, 0
468
469    # See whether to update the cached PDF document.
470    if _pdfDoc is None or url != _pdfURL:
471        # Release any cached document or URL.
472        _pdfDoc = CGPDFDocumentCreateWithURL(url);
473        if _pdfDoc is not None:
474            pdfMediaRect = CGPDFDocumentGetMediaBox(_pdfDoc, 1)
475            _width = pdfMediaRect.size.width;
476            _height = pdfMediaRect.size.height;
477            # Keep the URL of the PDF file being cached.
478            _pdfURL = url
479
480        else:
481            _pdfURL = None
482
483    if _pdfDoc is not None:
484        return _pdfDoc, _width, _height
485
486    else:
487        return None, 0, 0
488
489# Defining this scales the content down by 1/3.
490DOSCALING=True
491
492def TilePDFNoBuffer(context, url):
493    # The amount of area to tile should really be based on the
494    # window/document. Here it is hard coded to a US Letter
495    # size document. This may draw too many or too few tiles
496    # for the area actually being filled.
497    fillwidth = 612.0
498    fillheight = 792.0
499    extraOffset = 6.0
500    pdfDoc, tileX, tileY = getThePDFDoc(url)
501    if pdfDoc is None:
502        print >>sys.stderr, "Couldn't get the PDF document!"
503        return
504
505    if DOSCALING:
506        # Make the tiles 1/3 the size of the PDF document.
507        tileX /= 3
508        tileY /= 3
509        extraOffset /= 3
510
511    # Space the tiles by the tile width and height
512    # plus extraOffset units in each dimension.
513    tileOffsetX = extraOffset + tileX;
514    tileOffsetY = extraOffset + tileY;
515
516    # Tile the PDF document.
517    for h in range(0, int(fillheight), int(tileOffsetY)):
518        for w in range(0, int(fillwidth), int(tileOffsetX)):
519            CGContextDrawPDFDocument(context,
520                CGRectMake(w, h, tileX, tileY), pdfDoc, 1);
521
522def TilePDFWithOffscreenBitmap(context, url):
523    # Again this should really be computed based on
524    # the area intended to be tiled.
525    fillwidth = 612.0
526    fillheight = 792.0
527    extraOffset = 6.0
528
529    pdfDoc, tileX, tileY = getThePDFDoc(url)
530    if pdfDoc is None:
531        print >>sys.stderr, "Couldn't get the PDF document"
532        return
533
534    if DOSCALING:
535        # Make the tiles 1/3 the size of the PDF document.
536        tileX /= 3
537        tileY /= 3
538	extraOffset /= 3
539
540    # Space the tiles by the tile width and height
541    # plus extraOffset units in each dimension.
542    tileOffsetX = extraOffset + tileX;
543    tileOffsetY = extraOffset + tileY;
544
545    # Since the bitmap context is for use with the display
546    # and should capture alpha, these are the values
547    # to pass to createRGBBitmapContext.
548    useDisplayColorSpace = True;
549    needTransparentBitmap = True;
550    bitmapContext = createRGBBitmapContext(tileX, tileY,
551					useDisplayColorSpace,
552					needTransparentBitmap);
553    if bitmapContext is None:
554        print >>sys.stderr, "Couldn't create bitmap context!"
555        return
556
557    # Draw the PDF document one time into the bitmap context.
558    CGContextDrawPDFDocument(bitmapContext,
559		    CGRectMake(0, 0, tileX, tileY), pdfDoc, 1);
560
561    # Create an image from the raster data. Calling
562    # createImageFromBitmapContext gives up ownership
563    # of the raster data used by the context.
564    image = createImageFromBitmapContext(bitmapContext);
565
566    # Release the context now that the image is created.
567    del bitmapContext
568
569    if image is None:
570        return
571
572    # Now tile the image.
573    for h in range(0, int(fillheight), int(tileOffsetY)):
574        for w in range(0, int(fillwidth), int(tileOffsetX)):
575            CGContextDrawImage(context, CGRectMake(w, h, tileX, tileY), image)
576
577
578def createLayerWithImageForContext(c, url):
579    layerSize = CGSize()
580    pdfDoc, layerSize.width, layerSize.height = getThePDFDoc(url)
581    if pdfDoc is None:
582        return None
583
584    if DOSCALING:
585        # Make the layer 1/3 the size of the PDF document.
586        layerSize.width /= 3
587        layerSize.height /= 3
588
589    # Create the layer to draw into.
590    layer = CGLayerCreateWithContext(c, layerSize, None)
591    if layer is None:
592        return NULL
593
594    # Get the context corresponding to the layer. Note
595    # that this is a 'Get' function so the code must
596    # not release the context.
597    layerContext = CGLayerGetContext(layer)
598    if layerContext is None:
599        return None
600
601    # Draw the PDF document into the layer.
602    CGContextDrawPDFDocument(layerContext,
603        CGRectMake(0, 0, layerSize.width, layerSize.height), pdfDoc, 1);
604
605    # Now the layer has the contents needed.
606    return layer
607
608def TilePDFWithCGLayer(context, url):
609    # Again this should really be computed based on
610    # the area intended to be tiled.
611    fillwidth = 612.0
612    fillheight = 792.0
613    layer = createLayerWithImageForContext(context, url)
614    if layer is None:
615        print >>sys.stderr, "Couldn't create the layer!"
616        return
617
618    # Compute the tile size and offset.
619    s = CGLayerGetSize(layer);
620    tileX = s.width
621    tileY = s.height
622
623    if DOSCALING:
624        # Space the tiles by the tile width and height
625        # plus an extra 2 units in each dimension.
626        tileOffsetX = 2.0 + tileX;
627        tileOffsetY = 2.0 + tileY;
628    else:
629	# Add 6 units to the offset in each direction
630	# if there is no scaling of the source PDF document.
631        tileOffsetX = 6. + tileX;
632        tileOffsetY = 6. + tileY;
633
634    # Now draw the contents of the layer to the context.
635    # The layer is drawn at its true size (the size of
636    # the tile) with its origin located at the corner
637    # of each tile.
638    for h in range(0, int(fillheight), int(tileOffsetY)):
639        for w in range(0, int(fillwidth), int(tileOffsetX)):
640            CGContextDrawLayerAtPoint(context, CGPointMake(w, h), layer)
641