/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-9 by Raw Material Software Ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License (Version 2), as published by the Free Software Foundation. A copy of the license is included in the JUCE distribution, or can be found online at www.gnu.org/licenses. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ // (This file gets included by juce_mac_NativeCode.mm, rather than being // compiled on its own). #if JUCE_INCLUDED_FILE //============================================================================== class CoreGraphicsImage : public Image { public: //============================================================================== CoreGraphicsImage (const PixelFormat format_, const int imageWidth_, const int imageHeight_, const bool clearImage) : Image (format_, imageWidth_, imageHeight_, clearImage) { CGColorSpaceRef colourSpace = format == Image::SingleChannel ? CGColorSpaceCreateDeviceGray() : CGColorSpaceCreateDeviceRGB(); context = CGBitmapContextCreate (imageData, imageWidth, imageHeight, 8, lineStride, colourSpace, getCGImageFlags (*this)); CGColorSpaceRelease (colourSpace); } ~CoreGraphicsImage() { CGContextRelease (context); } LowLevelGraphicsContext* createLowLevelContext(); //============================================================================== static CGImageRef createImage (const Image& juceImage, const bool forAlpha, CGColorSpaceRef colourSpace) throw() { const CoreGraphicsImage* nativeImage = dynamic_cast (&juceImage); if (nativeImage != 0 && (juceImage.getFormat() == Image::SingleChannel || ! forAlpha)) { return CGBitmapContextCreateImage (nativeImage->context); } else { const Image::BitmapData srcData (juceImage, 0, 0, juceImage.getWidth(), juceImage.getHeight()); CGDataProviderRef provider = CGDataProviderCreateWithData (0, srcData.data, srcData.lineStride * srcData.pixelStride, 0); CGImageRef imageRef = CGImageCreate (srcData.width, srcData.height, 8, srcData.pixelStride * 8, srcData.lineStride, colourSpace, getCGImageFlags (juceImage), provider, 0, true, kCGRenderingIntentDefault); CGDataProviderRelease (provider); return imageRef; } } #if JUCE_MAC static NSImage* createNSImage (const Image& image) { const ScopedAutoReleasePool pool; NSImage* im = [[NSImage alloc] init]; [im setSize: NSMakeSize (image.getWidth(), image.getHeight())]; [im lockFocus]; CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB(); CGImageRef imageRef = createImage (image, false, colourSpace); CGColorSpaceRelease (colourSpace); CGContextRef cg = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort]; CGContextDrawImage (cg, CGRectMake (0, 0, image.getWidth(), image.getHeight()), imageRef); CGImageRelease (imageRef); [im unlockFocus]; return im; } #endif //============================================================================== CGContextRef context; private: static CGBitmapInfo getCGImageFlags (const Image& image) throw() { #if JUCE_BIG_ENDIAN return image.getFormat() == Image::ARGB ? (kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big) : kCGBitmapByteOrderDefault; #else return image.getFormat() == Image::ARGB ? (kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little) : kCGBitmapByteOrderDefault; #endif } }; Image* Image::createNativeImage (const PixelFormat format, const int imageWidth, const int imageHeight, const bool clearImage) { #if USE_COREGRAPHICS_RENDERING return new CoreGraphicsImage (format == RGB ? ARGB : format, imageWidth, imageHeight, clearImage); #else return new Image (format, imageWidth, imageHeight, clearImage); #endif } //============================================================================== class CoreGraphicsContext : public LowLevelGraphicsContext { public: CoreGraphicsContext (CGContextRef context_, const float flipHeight_) : context (context_), flipHeight (flipHeight_), gradientLookupTable (0), numGradientLookupEntries (0) { CGContextRetain (context); CGContextSaveGState(context); CGContextSetShouldSmoothFonts (context, true); CGContextSetShouldAntialias (context, true); CGContextSetBlendMode (context, kCGBlendModeNormal); rgbColourSpace = CGColorSpaceCreateDeviceRGB(); greyColourSpace = CGColorSpaceCreateDeviceGray(); gradientCallbacks.version = 0; gradientCallbacks.evaluate = gradientCallback; gradientCallbacks.releaseInfo = 0; state = new SavedState(); } ~CoreGraphicsContext() { CGContextRestoreGState (context); CGContextRelease (context); CGColorSpaceRelease (rgbColourSpace); CGColorSpaceRelease (greyColourSpace); delete state; delete gradientLookupTable; } //============================================================================== bool isVectorDevice() const { return false; } void setOrigin (int x, int y) { CGContextTranslateCTM (context, x, -y); } bool clipToRectangle (const Rectangle& r) { CGContextClipToRect (context, CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight())); return ! isClipEmpty(); } bool clipToRectangleList (const RectangleList& clipRegion) { if (clipRegion.isEmpty()) { CGContextClipToRect (context, CGRectMake (0, 0, 0, 0)); return false; } else { const int numRects = clipRegion.getNumRectangles(); CGRect* const rects = new CGRect [numRects]; for (int i = 0; i < numRects; ++i) { const Rectangle& r = clipRegion.getRectangle(i); rects[i] = CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight()); } CGContextClipToRects (context, rects, numRects); delete[] rects; return ! isClipEmpty(); } } void excludeClipRectangle (const Rectangle& r) { RectangleList remaining (getClipBounds()); remaining.subtract (r); clipToRectangleList (remaining); } void clipToPath (const Path& path, const AffineTransform& transform) { createPath (path, transform); CGContextClip (context); } void clipToImageAlpha (const Image& sourceImage, const Rectangle& srcClip, const AffineTransform& transform) { if (! transform.isSingularity()) { Image* singleChannelImage = createAlphaChannelImage (sourceImage); CGImageRef image = CoreGraphicsImage::createImage (*singleChannelImage, true, greyColourSpace); flip(); AffineTransform t (AffineTransform::scale (1.0f, -1.0f).translated (0, sourceImage.getHeight()).followedBy (transform)); applyTransform (t); CGRect r = CGRectMake (0, 0, sourceImage.getWidth(), sourceImage.getHeight()); CGContextClipToMask (context, r, image); applyTransform (t.inverted()); flip(); CGImageRelease (image); deleteAlphaChannelImage (sourceImage, singleChannelImage); } } bool clipRegionIntersects (const Rectangle& r) { return getClipBounds().intersects (r); } const Rectangle getClipBounds() const { CGRect bounds = CGRectIntegral (CGContextGetClipBoundingBox (context)); return Rectangle (roundFloatToInt (bounds.origin.x), roundFloatToInt (flipHeight - (bounds.origin.y + bounds.size.height)), roundFloatToInt (bounds.size.width), roundFloatToInt (bounds.size.height)); } bool isClipEmpty() const { return CGRectIsEmpty (CGContextGetClipBoundingBox (context)); } //============================================================================== void saveState() { CGContextSaveGState (context); stateStack.add (new SavedState (*state)); } void restoreState() { CGContextRestoreGState (context); SavedState* const top = stateStack.getLast(); if (top != 0) { delete state; state = top; stateStack.removeLast (1, false); } else { jassertfalse // trying to pop with an empty stack! } } //============================================================================== void setFill (const FillType& fillType) { state->fillType = fillType; if (fillType.isColour()) { CGContextSetRGBFillColor (context, fillType.colour.getFloatRed(), fillType.colour.getFloatGreen(), fillType.colour.getFloatBlue(), fillType.colour.getFloatAlpha()); CGContextSetAlpha (context, 1.0f); } } void setOpacity (float newOpacity) { state->fillType.setOpacity (newOpacity); setFill (state->fillType); } void setInterpolationQuality (Graphics::ResamplingQuality quality) { CGContextSetInterpolationQuality (context, quality == Graphics::lowResamplingQuality ? kCGInterpolationLow : kCGInterpolationHigh); } //============================================================================== void fillRect (const Rectangle& r, const bool replaceExistingContents) { CGRect cgRect = CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight()); if (replaceExistingContents) { #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5 CGContextClearRect (context, cgRect); #else #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 if (CGContextDrawLinearGradient == 0) // (just a way of checking whether we're running in 10.5 or later) CGContextClearRect (context, cgRect); else #endif CGContextSetBlendMode (context, kCGBlendModeCopy); #endif fillRect (r, false); CGContextSetBlendMode (context, kCGBlendModeNormal); } else { if (state->fillType.isColour()) { CGContextFillRect (context, cgRect); } else if (state->fillType.isGradient()) { CGContextSaveGState (context); CGContextClipToRect (context, cgRect); drawGradient(); CGContextRestoreGState (context); } else { CGContextSaveGState (context); CGContextClipToRect (context, cgRect); drawImage (*(state->fillType.image), state->fillType.image->getBounds(), state->fillType.transform, true); CGContextRestoreGState (context); } } } void fillPath (const Path& path, const AffineTransform& transform) { CGContextSaveGState (context); if (state->fillType.isColour()) { flip(); applyTransform (transform); createPath (path); if (path.isUsingNonZeroWinding()) CGContextFillPath (context); else CGContextEOFillPath (context); } else { createPath (path, transform); if (path.isUsingNonZeroWinding()) CGContextClip (context); else CGContextEOClip (context); if (state->fillType.isGradient()) drawGradient(); else drawImage (*(state->fillType.image), state->fillType.image->getBounds(), state->fillType.transform, true); } CGContextRestoreGState (context); } void drawImage (const Image& sourceImage, const Rectangle& srcClip, const AffineTransform& transform, const bool fillEntireClipAsTiles) { jassert (sourceImage.getBounds().contains (srcClip)); CGImageRef fullImage = CoreGraphicsImage::createImage (sourceImage, false, rgbColourSpace); CGImageRef image = fullImage; if (srcClip != sourceImage.getBounds()) { image = CGImageCreateWithImageInRect (fullImage, CGRectMake (srcClip.getX(), srcClip.getY(), srcClip.getWidth(), srcClip.getHeight())); CGImageRelease (fullImage); } CGContextSaveGState (context); CGContextSetAlpha (context, state->fillType.getOpacity()); flip(); applyTransform (AffineTransform::scale (1.0f, -1.0f).translated (0, srcClip.getHeight()).followedBy (transform)); CGRect imageRect = CGRectMake (0, 0, srcClip.getWidth(), srcClip.getHeight()); if (fillEntireClipAsTiles) { #if JUCE_IPHONE || (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) CGContextDrawTiledImage (context, imageRect, image); #else #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 if (CGContextDrawTiledImage != 0) CGContextDrawTiledImage (context, imageRect, image); else #endif { // Fallback to manually doing a tiled fill on 10.4 CGRect clip = CGRectIntegral (CGContextGetClipBoundingBox (context)); const int iw = srcClip.getWidth(); const int ih = srcClip.getHeight(); int x = 0, y = 0; while (x > clip.origin.x) x -= iw; while (y > clip.origin.y) y -= ih; const int right = (int) (clip.origin.x + clip.size.width); const int bottom = (int) (clip.origin.y + clip.size.height); while (y < bottom) { for (int x2 = x; x2 < right; x2 += iw) CGContextDrawImage (context, CGRectMake (x2, y, iw, ih), image); y += ih; } } #endif } else { CGContextDrawImage (context, imageRect, image); } CGImageRelease (image); // (This causes a memory bug in iPhone sim 3.0 - try upgrading to a later version if you hit this) CGContextRestoreGState (context); } //============================================================================== void drawLine (double x1, double y1, double x2, double y2) { CGContextSetLineCap (context, kCGLineCapSquare); CGContextSetLineWidth (context, 1.0f); CGContextSetRGBStrokeColor (context, state->fillType.colour.getFloatRed(), state->fillType.colour.getFloatGreen(), state->fillType.colour.getFloatBlue(), state->fillType.colour.getFloatAlpha()); CGPoint line[] = { { (float) x1 + 0.5f, flipHeight - ((float) y1 + 0.5f) }, { (float) x2 + 0.5f, flipHeight - ((float) y2 + 0.5f) } }; CGContextStrokeLineSegments (context, line, 1); } void drawVerticalLine (const int x, double top, double bottom) { CGContextFillRect (context, CGRectMake (x, flipHeight - (float) bottom, 1.0f, (float) (bottom - top))); } void drawHorizontalLine (const int y, double left, double right) { CGContextFillRect (context, CGRectMake ((float) left, flipHeight - (y + 1.0f), (float) (right - left), 1.0f)); } void setFont (const Font& newFont) { if (state->font != newFont) { state->fontRef = 0; state->font = newFont; MacTypeface* mf = dynamic_cast ((Typeface*) state->font.getTypeface()); if (mf != 0) { state->fontRef = mf->fontRef; CGContextSetFont (context, state->fontRef); CGContextSetFontSize (context, state->font.getHeight() * mf->fontHeightToCGSizeFactor); state->fontTransform = mf->renderingTransform; state->fontTransform.a *= state->font.getHorizontalScale(); CGContextSetTextMatrix (context, state->fontTransform); } } } const Font getFont() { return state->font; } void drawGlyph (int glyphNumber, const AffineTransform& transform) { if (state->fontRef != 0 && state->fillType.isColour()) { if (transform.isOnlyTranslation()) { CGGlyph g = glyphNumber; CGContextShowGlyphsAtPoint (context, transform.getTranslationX(), flipHeight - roundFloatToInt (transform.getTranslationY()), &g, 1); } else { CGContextSaveGState (context); flip(); applyTransform (transform); CGAffineTransform t = state->fontTransform; t.d = -t.d; CGContextSetTextMatrix (context, t); CGGlyph g = glyphNumber; CGContextShowGlyphsAtPoint (context, 0, 0, &g, 1); CGContextSetTextMatrix (context, state->fontTransform); CGContextRestoreGState (context); } } else { Path p; Font& f = state->font; f.getTypeface()->getOutlineForGlyph (glyphNumber, p); fillPath (p, AffineTransform::scale (f.getHeight() * f.getHorizontalScale(), f.getHeight()) .followedBy (transform)); } } private: CGContextRef context; const float flipHeight; CGColorSpaceRef rgbColourSpace, greyColourSpace; CGFunctionCallbacks gradientCallbacks; struct SavedState { SavedState() throw() : font (1.0f), fontRef (0), fontTransform (CGAffineTransformIdentity) { } SavedState (const SavedState& other) throw() : fillType (other.fillType), font (other.font), fontRef (other.fontRef), fontTransform (other.fontTransform) { } ~SavedState() throw() { } FillType fillType; Font font; CGFontRef fontRef; CGAffineTransform fontTransform; }; SavedState* state; OwnedArray stateStack; PixelARGB* gradientLookupTable; int numGradientLookupEntries; static void gradientCallback (void* info, const CGFloat* inData, CGFloat* outData) { const CoreGraphicsContext* const g = (const CoreGraphicsContext*) info; const int index = roundFloatToInt (g->numGradientLookupEntries * inData[0]); PixelARGB colour (g->gradientLookupTable [jlimit (0, g->numGradientLookupEntries, index)]); colour.unpremultiply(); outData[0] = colour.getRed() / 255.0f; outData[1] = colour.getGreen() / 255.0f; outData[2] = colour.getBlue() / 255.0f; outData[3] = colour.getAlpha() / 255.0f; } CGShadingRef createGradient (const AffineTransform& transform, ColourGradient gradient) throw() { delete gradientLookupTable; gradientLookupTable = gradient.createLookupTable (transform, numGradientLookupEntries); --numGradientLookupEntries; CGShadingRef result = 0; CGFunctionRef function = CGFunctionCreate ((void*) this, 1, 0, 4, 0, &gradientCallbacks); CGPoint p1 (CGPointMake (gradient.x1, gradient.y1)); if (gradient.isRadial) { result = CGShadingCreateRadial (rgbColourSpace, p1, 0, p1, hypotf (gradient.x1 - gradient.x2, gradient.y1 - gradient.y2), function, true, true); } else { result = CGShadingCreateAxial (rgbColourSpace, p1, CGPointMake (gradient.x2, gradient.y2), function, true, true); } CGFunctionRelease (function); return result; } void drawGradient() throw() { flip(); applyTransform (state->fillType.transform); CGContextSetInterpolationQuality (context, kCGInterpolationDefault); // (This is required for 10.4, where there's a crash if // you draw a gradient with high quality interp enabled). CGShadingRef shading = createGradient (state->fillType.transform, *(state->fillType.gradient)); CGContextSetAlpha (context, state->fillType.getOpacity()); CGContextDrawShading (context, shading); CGShadingRelease (shading); } void createPath (const Path& path) const throw() { CGContextBeginPath (context); Path::Iterator i (path); while (i.next()) { switch (i.elementType) { case Path::Iterator::startNewSubPath: CGContextMoveToPoint (context, i.x1, i.y1); break; case Path::Iterator::lineTo: CGContextAddLineToPoint (context, i.x1, i.y1); break; case Path::Iterator::quadraticTo: CGContextAddQuadCurveToPoint (context, i.x1, i.y1, i.x2, i.y2); break; case Path::Iterator::cubicTo: CGContextAddCurveToPoint (context, i.x1, i.y1, i.x2, i.y2, i.x3, i.y3); break; case Path::Iterator::closePath: CGContextClosePath (context); break; default: jassertfalse break; } } } void createPath (const Path& path, const AffineTransform& transform) const throw() { CGContextBeginPath (context); Path::Iterator i (path); while (i.next()) { switch (i.elementType) { case Path::Iterator::startNewSubPath: transform.transformPoint (i.x1, i.y1); CGContextMoveToPoint (context, i.x1, flipHeight - i.y1); break; case Path::Iterator::lineTo: transform.transformPoint (i.x1, i.y1); CGContextAddLineToPoint (context, i.x1, flipHeight - i.y1); break; case Path::Iterator::quadraticTo: transform.transformPoint (i.x1, i.y1); transform.transformPoint (i.x2, i.y2); CGContextAddQuadCurveToPoint (context, i.x1, flipHeight - i.y1, i.x2, flipHeight - i.y2); break; case Path::Iterator::cubicTo: transform.transformPoint (i.x1, i.y1); transform.transformPoint (i.x2, i.y2); transform.transformPoint (i.x3, i.y3); CGContextAddCurveToPoint (context, i.x1, flipHeight - i.y1, i.x2, flipHeight - i.y2, i.x3, flipHeight - i.y3); break; case Path::Iterator::closePath: CGContextClosePath (context); break; default: jassertfalse break; } } } static Image* createAlphaChannelImage (const Image& im) throw() { if (im.getFormat() == Image::SingleChannel) return const_cast (&im); return im.createCopyOfAlphaChannel(); } static void deleteAlphaChannelImage (const Image& im, Image* const alphaIm) throw() { if (im.getFormat() != Image::SingleChannel) delete alphaIm; } void flip() const throw() { CGContextConcatCTM (context, CGAffineTransformMake (1, 0, 0, -1, 0, flipHeight)); } void applyTransform (const AffineTransform& transform) const throw() { CGAffineTransform t; t.a = transform.mat00; t.b = transform.mat10; t.c = transform.mat01; t.d = transform.mat11; t.tx = transform.mat02; t.ty = transform.mat12; CGContextConcatCTM (context, t); } float flipY (float y) const throw() { return flipHeight - y; } }; LowLevelGraphicsContext* CoreGraphicsImage::createLowLevelContext() { return new CoreGraphicsContext (context, imageHeight); } #endif