1/* 2 ============================================================================== 3 4 This file is part of the JUCE library. 5 Copyright (c) 2020 - Raw Material Software Limited 6 7 JUCE is an open source library subject to commercial or open-source 8 licensing. 9 10 By using JUCE, you agree to the terms of both the JUCE 6 End-User License 11 Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). 12 13 End User License Agreement: www.juce.com/juce-6-licence 14 Privacy Policy: www.juce.com/juce-privacy-policy 15 16 Or: You may also use this code under the terms of the GPL v3 (see 17 www.gnu.org/licenses). 18 19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER 20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE 21 DISCLAIMED. 22 23 ============================================================================== 24*/ 25 26namespace juce 27{ 28 29#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_8 30 #define __nullable 31#endif 32 33//============================================================================== 34// This class has been renamed from CoreGraphicsImage to avoid a symbol 35// collision in Pro Tools 2019.12 and possibly 2020 depending on the Pro Tools 36// release schedule. 37class CoreGraphicsPixelData : public ImagePixelData 38{ 39public: 40 CoreGraphicsPixelData (const Image::PixelFormat format, int w, int h, bool clearImage) 41 : ImagePixelData (format, w, h) 42 { 43 pixelStride = format == Image::RGB ? 3 : ((format == Image::ARGB) ? 4 : 1); 44 lineStride = (pixelStride * jmax (1, width) + 3) & ~3; 45 46 auto numComponents = (size_t) lineStride * (size_t) jmax (1, height); 47 48 # if JUCE_MAC && defined (MAC_OS_X_VERSION_10_14) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_14 49 // This version of the SDK intermittently requires a bit of extra space 50 // at the end of the image data. This feels like something has gone 51 // wrong in Apple's code. 52 numComponents += (size_t) lineStride; 53 #endif 54 55 imageDataHolder->data.allocate (numComponents, clearImage); 56 57 auto colourSpace = detail::ColorSpacePtr { CGColorSpaceCreateWithName ((format == Image::SingleChannel) ? kCGColorSpaceGenericGrayGamma2_2 58 : kCGColorSpaceSRGB) }; 59 60 context = detail::ContextPtr { CGBitmapContextCreate (imageDataHolder->data, (size_t) width, (size_t) height, 8, (size_t) lineStride, 61 colourSpace.get(), getCGImageFlags (format)) }; 62 } 63 64 ~CoreGraphicsPixelData() override 65 { 66 freeCachedImageRef(); 67 } 68 69 std::unique_ptr<LowLevelGraphicsContext> createLowLevelContext() override 70 { 71 freeCachedImageRef(); 72 sendDataChangeMessage(); 73 return std::make_unique<CoreGraphicsContext> (context.get(), height); 74 } 75 76 void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override 77 { 78 bitmap.data = imageDataHolder->data + x * pixelStride + y * lineStride; 79 bitmap.pixelFormat = pixelFormat; 80 bitmap.lineStride = lineStride; 81 bitmap.pixelStride = pixelStride; 82 83 if (mode != Image::BitmapData::readOnly) 84 { 85 freeCachedImageRef(); 86 sendDataChangeMessage(); 87 } 88 } 89 90 ImagePixelData::Ptr clone() override 91 { 92 auto im = new CoreGraphicsPixelData (pixelFormat, width, height, false); 93 memcpy (im->imageDataHolder->data, imageDataHolder->data, (size_t) (lineStride * height)); 94 return *im; 95 } 96 97 std::unique_ptr<ImageType> createType() const override { return std::make_unique<NativeImageType>(); } 98 99 //============================================================================== 100 static CGImageRef getCachedImageRef (const Image& juceImage, CGColorSpaceRef colourSpace) 101 { 102 auto cgim = dynamic_cast<CoreGraphicsPixelData*> (juceImage.getPixelData()); 103 104 if (cgim != nullptr && cgim->cachedImageRef != nullptr) 105 { 106 CGImageRetain (cgim->cachedImageRef.get()); 107 return cgim->cachedImageRef.get(); 108 } 109 110 CGImageRef ref = createImage (juceImage, colourSpace, false); 111 112 if (cgim != nullptr) 113 cgim->cachedImageRef.reset (CGImageRetain (ref)); 114 115 return ref; 116 } 117 118 static CGImageRef createImage (const Image& juceImage, CGColorSpaceRef colourSpace, bool mustOutliveSource) 119 { 120 const Image::BitmapData srcData (juceImage, Image::BitmapData::readOnly); 121 detail::DataProviderPtr provider; 122 123 if (mustOutliveSource) 124 { 125 CFDataRef data = CFDataCreate (nullptr, (const UInt8*) srcData.data, (CFIndex) ((size_t) srcData.lineStride * (size_t) srcData.height)); 126 provider = detail::DataProviderPtr { CGDataProviderCreateWithCFData (data) }; 127 CFRelease (data); 128 } 129 else 130 { 131 auto* imageDataContainer = [] (const Image& img) -> HeapBlockContainer::Ptr* 132 { 133 if (auto* cgim = dynamic_cast<CoreGraphicsPixelData*> (img.getPixelData())) 134 return new HeapBlockContainer::Ptr (cgim->imageDataHolder); 135 136 return nullptr; 137 } (juceImage); 138 139 provider = detail::DataProviderPtr { CGDataProviderCreateWithData (imageDataContainer, 140 srcData.data, 141 (size_t) srcData.lineStride * (size_t) srcData.height, 142 [] (void * __nullable info, const void*, size_t) { delete (HeapBlockContainer::Ptr*) info; }) }; 143 } 144 145 CGImageRef imageRef = CGImageCreate ((size_t) srcData.width, 146 (size_t) srcData.height, 147 8, 148 (size_t) srcData.pixelStride * 8, 149 (size_t) srcData.lineStride, 150 colourSpace, getCGImageFlags (juceImage.getFormat()), provider.get(), 151 nullptr, true, kCGRenderingIntentDefault); 152 153 return imageRef; 154 } 155 156 //============================================================================== 157 detail::ContextPtr context; 158 detail::ImagePtr cachedImageRef; 159 160 struct HeapBlockContainer : public ReferenceCountedObject 161 { 162 using Ptr = ReferenceCountedObjectPtr<HeapBlockContainer>; 163 HeapBlock<uint8> data; 164 }; 165 166 HeapBlockContainer::Ptr imageDataHolder = new HeapBlockContainer(); 167 int pixelStride, lineStride; 168 169private: 170 void freeCachedImageRef() 171 { 172 cachedImageRef.reset(); 173 } 174 175 static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format) 176 { 177 #if JUCE_BIG_ENDIAN 178 return format == Image::ARGB ? (kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big) : kCGBitmapByteOrderDefault; 179 #else 180 return format == Image::ARGB ? (kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little) : kCGBitmapByteOrderDefault; 181 #endif 182 } 183 184 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreGraphicsPixelData) 185}; 186 187ImagePixelData::Ptr NativeImageType::create (Image::PixelFormat format, int width, int height, bool clearImage) const 188{ 189 return *new CoreGraphicsPixelData (format == Image::RGB ? Image::ARGB : format, width, height, clearImage); 190} 191 192//============================================================================== 193struct ScopedCGContextState 194{ 195 explicit ScopedCGContextState (CGContextRef c) : context (c) { CGContextSaveGState (context); } 196 ~ScopedCGContextState() { CGContextRestoreGState (context); } 197 198 CGContextRef context; 199}; 200 201//============================================================================== 202CoreGraphicsContext::CoreGraphicsContext (CGContextRef c, float h) 203 : context (c), 204 flipHeight (h), 205 state (new SavedState()) 206{ 207 CGContextRetain (context.get()); 208 CGContextSaveGState (context.get()); 209 210 #if JUCE_MAC 211 bool enableFontSmoothing 212 #if JUCE_DISABLE_COREGRAPHICS_FONT_SMOOTHING 213 = false; 214 #else 215 = true; 216 #endif 217 218 CGContextSetShouldSmoothFonts (context.get(), enableFontSmoothing); 219 CGContextSetAllowsFontSmoothing (context.get(), enableFontSmoothing); 220 #endif 221 222 CGContextSetShouldAntialias (context.get(), true); 223 CGContextSetBlendMode (context.get(), kCGBlendModeNormal); 224 rgbColourSpace.reset (CGColorSpaceCreateWithName (kCGColorSpaceSRGB)); 225 greyColourSpace.reset (CGColorSpaceCreateWithName (kCGColorSpaceGenericGrayGamma2_2)); 226 setFont (Font()); 227} 228 229CoreGraphicsContext::~CoreGraphicsContext() 230{ 231 CGContextRestoreGState (context.get()); 232} 233 234//============================================================================== 235void CoreGraphicsContext::setOrigin (Point<int> o) 236{ 237 CGContextTranslateCTM (context.get(), o.x, -o.y); 238 239 if (lastClipRectIsValid) 240 lastClipRect.translate (-o.x, -o.y); 241} 242 243void CoreGraphicsContext::addTransform (const AffineTransform& transform) 244{ 245 applyTransform (AffineTransform::verticalFlip ((float) flipHeight) 246 .followedBy (transform) 247 .translated (0, (float) -flipHeight) 248 .scaled (1.0f, -1.0f)); 249 lastClipRectIsValid = false; 250 251 jassert (getPhysicalPixelScaleFactor() > 0.0f); 252} 253 254float CoreGraphicsContext::getPhysicalPixelScaleFactor() 255{ 256 auto t = CGContextGetUserSpaceToDeviceSpaceTransform (context.get()); 257 auto determinant = (t.a * t.d) - (t.c * t.b); 258 259 return (float) std::sqrt (std::abs (determinant)); 260} 261 262bool CoreGraphicsContext::clipToRectangle (const Rectangle<int>& r) 263{ 264 CGContextClipToRect (context.get(), CGRectMake (r.getX(), flipHeight - r.getBottom(), 265 r.getWidth(), r.getHeight())); 266 267 if (lastClipRectIsValid) 268 { 269 // This is actually incorrect, because the actual clip region may be complex, and 270 // clipping its bounds to a rect may not be right... But, removing this shortcut 271 // doesn't actually fix anything because CoreGraphics also ignores complex regions 272 // when calculating the resultant clip bounds, and makes the same mistake! 273 lastClipRect = lastClipRect.getIntersection (r); 274 return ! lastClipRect.isEmpty(); 275 } 276 277 return ! isClipEmpty(); 278} 279 280bool CoreGraphicsContext::clipToRectangleListWithoutTest (const RectangleList<int>& clipRegion) 281{ 282 if (clipRegion.isEmpty()) 283 { 284 CGContextClipToRect (context.get(), CGRectZero); 285 lastClipRectIsValid = true; 286 lastClipRect = Rectangle<int>(); 287 return false; 288 } 289 290 auto numRects = (size_t) clipRegion.getNumRectangles(); 291 HeapBlock<CGRect> rects (numRects); 292 293 int i = 0; 294 for (auto& r : clipRegion) 295 rects[i++] = CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight()); 296 297 CGContextClipToRects (context.get(), rects, numRects); 298 lastClipRectIsValid = false; 299 return true; 300} 301 302bool CoreGraphicsContext::clipToRectangleList (const RectangleList<int>& clipRegion) 303{ 304 return clipToRectangleListWithoutTest (clipRegion) && ! isClipEmpty(); 305} 306 307void CoreGraphicsContext::excludeClipRectangle (const Rectangle<int>& r) 308{ 309 RectangleList<int> remaining (getClipBounds()); 310 remaining.subtract (r); 311 clipToRectangleListWithoutTest (remaining); 312} 313 314void CoreGraphicsContext::clipToPath (const Path& path, const AffineTransform& transform) 315{ 316 createPath (path, transform); 317 318 if (path.isUsingNonZeroWinding()) 319 CGContextClip (context.get()); 320 else 321 CGContextEOClip (context.get()); 322 323 lastClipRectIsValid = false; 324} 325 326void CoreGraphicsContext::clipToImageAlpha (const Image& sourceImage, const AffineTransform& transform) 327{ 328 if (! transform.isSingularity()) 329 { 330 Image singleChannelImage (sourceImage); 331 332 if (sourceImage.getFormat() != Image::SingleChannel) 333 singleChannelImage = sourceImage.convertedToFormat (Image::SingleChannel); 334 335 auto image = detail::ImagePtr { CoreGraphicsPixelData::createImage (singleChannelImage, greyColourSpace.get(), true) }; 336 337 flip(); 338 auto t = AffineTransform::verticalFlip (sourceImage.getHeight()).followedBy (transform); 339 applyTransform (t); 340 341 auto r = convertToCGRect (sourceImage.getBounds()); 342 CGContextClipToMask (context.get(), r, image.get()); 343 344 applyTransform (t.inverted()); 345 flip(); 346 347 lastClipRectIsValid = false; 348 } 349} 350 351bool CoreGraphicsContext::clipRegionIntersects (const Rectangle<int>& r) 352{ 353 return getClipBounds().intersects (r); 354} 355 356Rectangle<int> CoreGraphicsContext::getClipBounds() const 357{ 358 if (! lastClipRectIsValid) 359 { 360 auto bounds = CGRectIntegral (CGContextGetClipBoundingBox (context.get())); 361 362 lastClipRectIsValid = true; 363 lastClipRect.setBounds (roundToInt (bounds.origin.x), 364 roundToInt (flipHeight - (bounds.origin.y + bounds.size.height)), 365 roundToInt (bounds.size.width), 366 roundToInt (bounds.size.height)); 367 } 368 369 return lastClipRect; 370} 371 372bool CoreGraphicsContext::isClipEmpty() const 373{ 374 return getClipBounds().isEmpty(); 375} 376 377//============================================================================== 378void CoreGraphicsContext::saveState() 379{ 380 CGContextSaveGState (context.get()); 381 stateStack.add (new SavedState (*state)); 382} 383 384void CoreGraphicsContext::restoreState() 385{ 386 CGContextRestoreGState (context.get()); 387 388 if (auto* top = stateStack.getLast()) 389 { 390 state.reset (top); 391 CGContextSetTextMatrix (context.get(), state->textMatrix); 392 393 stateStack.removeLast (1, false); 394 lastClipRectIsValid = false; 395 } 396 else 397 { 398 jassertfalse; // trying to pop with an empty stack! 399 } 400} 401 402void CoreGraphicsContext::beginTransparencyLayer (float opacity) 403{ 404 saveState(); 405 CGContextSetAlpha (context.get(), opacity); 406 CGContextBeginTransparencyLayer (context.get(), nullptr); 407} 408 409void CoreGraphicsContext::endTransparencyLayer() 410{ 411 CGContextEndTransparencyLayer (context.get()); 412 restoreState(); 413} 414 415//============================================================================== 416void CoreGraphicsContext::setFill (const FillType& fillType) 417{ 418 state->setFill (fillType); 419 420 if (fillType.isColour()) 421 { 422 CGContextSetRGBFillColor (context.get(), fillType.colour.getFloatRed(), fillType.colour.getFloatGreen(), 423 fillType.colour.getFloatBlue(), fillType.colour.getFloatAlpha()); 424 CGContextSetAlpha (context.get(), 1.0f); 425 } 426} 427 428void CoreGraphicsContext::setOpacity (float newOpacity) 429{ 430 state->fillType.setOpacity (newOpacity); 431 setFill (state->fillType); 432} 433 434void CoreGraphicsContext::setInterpolationQuality (Graphics::ResamplingQuality quality) 435{ 436 switch (quality) 437 { 438 case Graphics::lowResamplingQuality: CGContextSetInterpolationQuality (context.get(), kCGInterpolationNone); return; 439 case Graphics::mediumResamplingQuality: CGContextSetInterpolationQuality (context.get(), kCGInterpolationMedium); return; 440 case Graphics::highResamplingQuality: CGContextSetInterpolationQuality (context.get(), kCGInterpolationHigh); return; 441 default: return; 442 } 443} 444 445//============================================================================== 446void CoreGraphicsContext::fillRect (const Rectangle<int>& r, bool replaceExistingContents) 447{ 448 fillCGRect (CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight()), replaceExistingContents); 449} 450 451void CoreGraphicsContext::fillRect (const Rectangle<float>& r) 452{ 453 fillCGRect (CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight()), false); 454} 455 456void CoreGraphicsContext::fillCGRect (const CGRect& cgRect, bool replaceExistingContents) 457{ 458 if (replaceExistingContents) 459 { 460 CGContextSetBlendMode (context.get(), kCGBlendModeCopy); 461 fillCGRect (cgRect, false); 462 CGContextSetBlendMode (context.get(), kCGBlendModeNormal); 463 } 464 else 465 { 466 if (state->fillType.isColour()) 467 { 468 CGContextFillRect (context.get(), cgRect); 469 } 470 else 471 { 472 ScopedCGContextState scopedState (context.get()); 473 474 CGContextClipToRect (context.get(), cgRect); 475 476 if (state->fillType.isGradient()) 477 drawGradient(); 478 else 479 drawImage (state->fillType.image, state->fillType.transform, true); 480 } 481 } 482} 483 484void CoreGraphicsContext::fillPath (const Path& path, const AffineTransform& transform) 485{ 486 ScopedCGContextState scopedState (context.get()); 487 488 if (state->fillType.isColour()) 489 { 490 flip(); 491 applyTransform (transform); 492 createPath (path); 493 494 if (path.isUsingNonZeroWinding()) 495 CGContextFillPath (context.get()); 496 else 497 CGContextEOFillPath (context.get()); 498 } 499 else 500 { 501 createPath (path, transform); 502 503 if (path.isUsingNonZeroWinding()) 504 CGContextClip (context.get()); 505 else 506 CGContextEOClip (context.get()); 507 508 if (state->fillType.isGradient()) 509 drawGradient(); 510 else 511 drawImage (state->fillType.image, state->fillType.transform, true); 512 } 513} 514 515void CoreGraphicsContext::drawImage (const Image& sourceImage, const AffineTransform& transform) 516{ 517 drawImage (sourceImage, transform, false); 518} 519 520void CoreGraphicsContext::drawImage (const Image& sourceImage, const AffineTransform& transform, bool fillEntireClipAsTiles) 521{ 522 auto iw = sourceImage.getWidth(); 523 auto ih = sourceImage.getHeight(); 524 525 auto colourSpace = sourceImage.getFormat() == Image::PixelFormat::SingleChannel ? greyColourSpace.get() 526 : rgbColourSpace.get(); 527 auto image = detail::ImagePtr { CoreGraphicsPixelData::getCachedImageRef (sourceImage, colourSpace) }; 528 529 ScopedCGContextState scopedState (context.get()); 530 CGContextSetAlpha (context.get(), state->fillType.getOpacity()); 531 532 flip(); 533 applyTransform (AffineTransform::verticalFlip (ih).followedBy (transform)); 534 auto imageRect = CGRectMake (0, 0, iw, ih); 535 536 if (fillEntireClipAsTiles) 537 { 538 #if JUCE_IOS 539 CGContextDrawTiledImage (context.get(), imageRect, image.get()); 540 #else 541 // There's a bug in CGContextDrawTiledImage that makes it incredibly slow 542 // if it's doing a transformation - it's quicker to just draw lots of images manually 543 if (&CGContextDrawTiledImage != nullptr && transform.isOnlyTranslation()) 544 { 545 CGContextDrawTiledImage (context.get(), imageRect, image.get()); 546 } 547 else 548 { 549 // Fallback to manually doing a tiled fill 550 auto clip = CGRectIntegral (CGContextGetClipBoundingBox (context.get())); 551 552 int x = 0, y = 0; 553 while (x > clip.origin.x) x -= iw; 554 while (y > clip.origin.y) y -= ih; 555 556 auto right = (int) (clip.origin.x + clip.size.width); 557 auto bottom = (int) (clip.origin.y + clip.size.height); 558 559 while (y < bottom) 560 { 561 for (int x2 = x; x2 < right; x2 += iw) 562 CGContextDrawImage (context.get(), CGRectMake (x2, y, iw, ih), image.get()); 563 564 y += ih; 565 } 566 } 567 #endif 568 } 569 else 570 { 571 CGContextDrawImage (context.get(), imageRect, image.get()); 572 } 573} 574 575//============================================================================== 576void CoreGraphicsContext::drawLine (const Line<float>& line) 577{ 578 Path p; 579 p.addLineSegment (line, 1.0f); 580 fillPath (p, {}); 581} 582 583void CoreGraphicsContext::fillRectList (const RectangleList<float>& list) 584{ 585 HeapBlock<CGRect> rects (list.getNumRectangles()); 586 587 size_t num = 0; 588 589 for (auto& r : list) 590 rects[num++] = CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight()); 591 592 if (state->fillType.isColour()) 593 { 594 CGContextFillRects (context.get(), rects, num); 595 } 596 else 597 { 598 ScopedCGContextState scopedState (context.get()); 599 600 CGContextClipToRects (context.get(), rects, num); 601 602 if (state->fillType.isGradient()) 603 drawGradient(); 604 else 605 drawImage (state->fillType.image, state->fillType.transform, true); 606 } 607} 608 609void CoreGraphicsContext::setFont (const Font& newFont) 610{ 611 if (state->font != newFont) 612 { 613 state->fontRef = nullptr; 614 state->font = newFont; 615 616 if (auto osxTypeface = dynamic_cast<OSXTypeface*> (state->font.getTypeface())) 617 { 618 state->fontRef = osxTypeface->fontRef; 619 CGContextSetFont (context.get(), state->fontRef); 620 CGContextSetFontSize (context.get(), state->font.getHeight() * osxTypeface->fontHeightToPointsFactor); 621 622 state->textMatrix = osxTypeface->renderingTransform; 623 state->textMatrix.a *= state->font.getHorizontalScale(); 624 CGContextSetTextMatrix (context.get(), state->textMatrix); 625 state->inverseTextMatrix = CGAffineTransformInvert (state->textMatrix); 626 } 627 } 628} 629 630const Font& CoreGraphicsContext::getFont() 631{ 632 return state->font; 633} 634 635void CoreGraphicsContext::drawGlyph (int glyphNumber, const AffineTransform& transform) 636{ 637 if (state->fontRef != nullptr && state->fillType.isColour()) 638 { 639 auto cgTransformIsOnlyTranslation = [] (CGAffineTransform t) 640 { 641 return t.a == 1.0f && t.d == 1.0f && t.b == 0.0f && t.c == 0.0f; 642 }; 643 644 if (transform.isOnlyTranslation() && cgTransformIsOnlyTranslation (state->inverseTextMatrix)) 645 { 646 auto x = transform.mat02 + state->inverseTextMatrix.tx; 647 auto y = transform.mat12 + state->inverseTextMatrix.ty; 648 649 CGGlyph glyphs[1] = { (CGGlyph) glyphNumber }; 650 CGPoint positions[1] = { { x, flipHeight - roundToInt (y) } }; 651 CGContextShowGlyphsAtPositions (context.get(), glyphs, positions, 1); 652 } 653 else 654 { 655 ScopedCGContextState scopedState (context.get()); 656 657 flip(); 658 applyTransform (transform); 659 CGContextConcatCTM (context.get(), state->inverseTextMatrix); 660 auto cgTransform = state->textMatrix; 661 cgTransform.d = -cgTransform.d; 662 CGContextConcatCTM (context.get(), cgTransform); 663 664 CGGlyph glyphs[1] = { (CGGlyph) glyphNumber }; 665 CGPoint positions[1] = { { 0.0f, 0.0f } }; 666 CGContextShowGlyphsAtPositions (context.get(), glyphs, positions, 1); 667 } 668 } 669 else 670 { 671 Path p; 672 auto& f = state->font; 673 f.getTypeface()->getOutlineForGlyph (glyphNumber, p); 674 675 fillPath (p, AffineTransform::scale (f.getHeight() * f.getHorizontalScale(), f.getHeight()) 676 .followedBy (transform)); 677 } 678} 679 680bool CoreGraphicsContext::drawTextLayout (const AttributedString& text, const Rectangle<float>& area) 681{ 682 CoreTextTypeLayout::drawToCGContext (text, area, context.get(), (float) flipHeight); 683 return true; 684} 685 686CoreGraphicsContext::SavedState::SavedState() 687 : font (1.0f) 688{ 689} 690 691CoreGraphicsContext::SavedState::SavedState (const SavedState& other) 692 : fillType (other.fillType), font (other.font), fontRef (other.fontRef), 693 textMatrix (other.textMatrix), inverseTextMatrix (other.inverseTextMatrix), 694 gradient (other.gradient.get()) 695{ 696 if (gradient != nullptr) 697 CGGradientRetain (gradient.get()); 698} 699 700CoreGraphicsContext::SavedState::~SavedState() = default; 701 702void CoreGraphicsContext::SavedState::setFill (const FillType& newFill) 703{ 704 fillType = newFill; 705 gradient = nullptr; 706} 707 708static CGGradientRef createGradient (const ColourGradient& g, CGColorSpaceRef colourSpace) 709{ 710 auto numColours = g.getNumColours(); 711 auto data = (CGFloat*) alloca ((size_t) numColours * 5 * sizeof (CGFloat)); 712 auto locations = data; 713 auto components = data + numColours; 714 auto comps = components; 715 716 for (int i = 0; i < numColours; ++i) 717 { 718 auto colour = g.getColour (i); 719 *comps++ = (CGFloat) colour.getFloatRed(); 720 *comps++ = (CGFloat) colour.getFloatGreen(); 721 *comps++ = (CGFloat) colour.getFloatBlue(); 722 *comps++ = (CGFloat) colour.getFloatAlpha(); 723 locations[i] = (CGFloat) g.getColourPosition (i); 724 725 // There's a bug (?) in the way the CG renderer works where it seems 726 // to go wrong if you have two colour stops both at position 0.. 727 jassert (i == 0 || locations[i] != 0); 728 } 729 730 return CGGradientCreateWithColorComponents (colourSpace, components, locations, (size_t) numColours); 731} 732 733void CoreGraphicsContext::drawGradient() 734{ 735 flip(); 736 applyTransform (state->fillType.transform); 737 CGContextSetAlpha (context.get(), state->fillType.getOpacity()); 738 739 auto& g = *state->fillType.gradient; 740 741 if (state->gradient == nullptr) 742 state->gradient.reset (createGradient (g, rgbColourSpace.get())); 743 744 auto p1 = convertToCGPoint (g.point1); 745 auto p2 = convertToCGPoint (g.point2); 746 747 if (g.isRadial) 748 CGContextDrawRadialGradient (context.get(), state->gradient.get(), p1, 0, p1, g.point1.getDistanceFrom (g.point2), 749 kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); 750 else 751 CGContextDrawLinearGradient (context.get(), state->gradient.get(), p1, p2, 752 kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); 753} 754 755void CoreGraphicsContext::createPath (const Path& path) const 756{ 757 CGContextBeginPath (context.get()); 758 759 for (Path::Iterator i (path); i.next();) 760 { 761 switch (i.elementType) 762 { 763 case Path::Iterator::startNewSubPath: CGContextMoveToPoint (context.get(), i.x1, i.y1); break; 764 case Path::Iterator::lineTo: CGContextAddLineToPoint (context.get(), i.x1, i.y1); break; 765 case Path::Iterator::quadraticTo: CGContextAddQuadCurveToPoint (context.get(), i.x1, i.y1, i.x2, i.y2); break; 766 case Path::Iterator::cubicTo: CGContextAddCurveToPoint (context.get(), i.x1, i.y1, i.x2, i.y2, i.x3, i.y3); break; 767 case Path::Iterator::closePath: CGContextClosePath (context.get()); break; 768 default: jassertfalse; break; 769 } 770 } 771} 772 773void CoreGraphicsContext::createPath (const Path& path, const AffineTransform& transform) const 774{ 775 CGContextBeginPath (context.get()); 776 777 for (Path::Iterator i (path); i.next();) 778 { 779 switch (i.elementType) 780 { 781 case Path::Iterator::startNewSubPath: 782 transform.transformPoint (i.x1, i.y1); 783 CGContextMoveToPoint (context.get(), i.x1, flipHeight - i.y1); 784 break; 785 case Path::Iterator::lineTo: 786 transform.transformPoint (i.x1, i.y1); 787 CGContextAddLineToPoint (context.get(), i.x1, flipHeight - i.y1); 788 break; 789 case Path::Iterator::quadraticTo: 790 transform.transformPoints (i.x1, i.y1, i.x2, i.y2); 791 CGContextAddQuadCurveToPoint (context.get(), i.x1, flipHeight - i.y1, i.x2, flipHeight - i.y2); 792 break; 793 case Path::Iterator::cubicTo: 794 transform.transformPoints (i.x1, i.y1, i.x2, i.y2, i.x3, i.y3); 795 CGContextAddCurveToPoint (context.get(), i.x1, flipHeight - i.y1, i.x2, flipHeight - i.y2, i.x3, flipHeight - i.y3); 796 break; 797 case Path::Iterator::closePath: 798 CGContextClosePath (context.get()); break; 799 default: 800 jassertfalse; 801 break; 802 } 803 } 804} 805 806void CoreGraphicsContext::flip() const 807{ 808 CGContextConcatCTM (context.get(), CGAffineTransformMake (1, 0, 0, -1, 0, flipHeight)); 809} 810 811void CoreGraphicsContext::applyTransform (const AffineTransform& transform) const 812{ 813 CGAffineTransform t; 814 t.a = transform.mat00; 815 t.b = transform.mat10; 816 t.c = transform.mat01; 817 t.d = transform.mat11; 818 t.tx = transform.mat02; 819 t.ty = transform.mat12; 820 CGContextConcatCTM (context.get(), t); 821} 822 823//============================================================================== 824#if USE_COREGRAPHICS_RENDERING && JUCE_USE_COREIMAGE_LOADER 825Image juce_loadWithCoreImage (InputStream& input) 826{ 827 struct MemoryBlockHolder : public ReferenceCountedObject 828 { 829 using Ptr = ReferenceCountedObjectPtr<MemoryBlockHolder>; 830 MemoryBlock block; 831 }; 832 833 MemoryBlockHolder::Ptr memBlockHolder = new MemoryBlockHolder(); 834 input.readIntoMemoryBlock (memBlockHolder->block, -1); 835 836 #if JUCE_IOS 837 JUCE_AUTORELEASEPOOL 838 #endif 839 { 840 #if JUCE_IOS 841 if (UIImage* uiImage = [UIImage imageWithData: [NSData dataWithBytesNoCopy: memBlockHolder->block.getData() 842 length: memBlockHolder->block.getSize() 843 freeWhenDone: NO]]) 844 { 845 CGImageRef loadedImage = uiImage.CGImage; 846 847 #else 848 auto provider = detail::DataProviderPtr { CGDataProviderCreateWithData (new MemoryBlockHolder::Ptr (memBlockHolder), 849 memBlockHolder->block.getData(), 850 memBlockHolder->block.getSize(), 851 [] (void * __nullable info, const void*, size_t) { delete (MemoryBlockHolder::Ptr*) info; }) }; 852 auto imageSource = CGImageSourceCreateWithDataProvider (provider.get(), nullptr); 853 854 if (imageSource != nullptr) 855 { 856 auto loadedImage = CGImageSourceCreateImageAtIndex (imageSource, 0, nullptr); 857 CFRelease (imageSource); 858 #endif 859 860 if (loadedImage != nullptr) 861 { 862 auto alphaInfo = CGImageGetAlphaInfo (loadedImage); 863 const bool hasAlphaChan = (alphaInfo != kCGImageAlphaNone 864 && alphaInfo != kCGImageAlphaNoneSkipLast 865 && alphaInfo != kCGImageAlphaNoneSkipFirst); 866 867 Image image (NativeImageType().create (Image::ARGB, // (CoreImage doesn't work with 24-bit images) 868 (int) CGImageGetWidth (loadedImage), 869 (int) CGImageGetHeight (loadedImage), 870 hasAlphaChan)); 871 872 auto cgImage = dynamic_cast<CoreGraphicsPixelData*> (image.getPixelData()); 873 jassert (cgImage != nullptr); // if USE_COREGRAPHICS_RENDERING is set, the CoreGraphicsPixelData class should have been used. 874 875 CGContextDrawImage (cgImage->context.get(), convertToCGRect (image.getBounds()), loadedImage); 876 CGContextFlush (cgImage->context.get()); 877 878 #if ! JUCE_IOS 879 CFRelease (loadedImage); 880 #endif 881 882 // Because it's impossible to create a truly 24-bit CG image, this flag allows a user 883 // to find out whether the file they just loaded the image from had an alpha channel or not. 884 image.getProperties()->set ("originalImageHadAlpha", hasAlphaChan); 885 return image; 886 } 887 } 888 } 889 890 return {}; 891} 892#endif 893 894Image juce_createImageFromCIImage (CIImage*, int, int); 895Image juce_createImageFromCIImage (CIImage* im, int w, int h) 896{ 897 auto cgImage = new CoreGraphicsPixelData (Image::ARGB, w, h, false); 898 899 CIContext* cic = [CIContext contextWithCGContext: cgImage->context.get() options: nil]; 900 [cic drawImage: im inRect: CGRectMake (0, 0, w, h) fromRect: CGRectMake (0, 0, w, h)]; 901 CGContextFlush (cgImage->context.get()); 902 903 return Image (*cgImage); 904} 905 906CGImageRef juce_createCoreGraphicsImage (const Image& juceImage, CGColorSpaceRef colourSpace, 907 const bool mustOutliveSource) 908{ 909 return CoreGraphicsPixelData::createImage (juceImage, colourSpace, mustOutliveSource); 910} 911 912CGContextRef juce_getImageContext (const Image& image) 913{ 914 if (auto cgi = dynamic_cast<CoreGraphicsPixelData*> (image.getPixelData())) 915 return cgi->context.get(); 916 917 jassertfalse; 918 return {}; 919} 920 921#if JUCE_IOS 922 Image juce_createImageFromUIImage (UIImage* img) 923 { 924 CGImageRef image = [img CGImage]; 925 926 Image retval (Image::ARGB, (int) CGImageGetWidth (image), (int) CGImageGetHeight (image), true); 927 CGContextRef ctx = juce_getImageContext (retval); 928 929 CGContextDrawImage (ctx, CGRectMake (0.0f, 0.0f, CGImageGetWidth (image), CGImageGetHeight (image)), image); 930 931 return retval; 932 } 933#endif 934 935#if JUCE_MAC 936 NSImage* imageToNSImage (const Image& image, float scaleFactor) 937 { 938 JUCE_AUTORELEASEPOOL 939 { 940 NSImage* im = [[NSImage alloc] init]; 941 auto requiredSize = NSMakeSize (image.getWidth() / scaleFactor, image.getHeight() / scaleFactor); 942 943 [im setSize: requiredSize]; 944 auto colourSpace = detail::ColorSpacePtr { CGColorSpaceCreateWithName (kCGColorSpaceSRGB) }; 945 auto imageRef = detail::ImagePtr { juce_createCoreGraphicsImage (image, colourSpace.get(), true) }; 946 947 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage: imageRef.get()]; 948 [imageRep setSize: requiredSize]; 949 [im addRepresentation: imageRep]; 950 [imageRep release]; 951 return im; 952 } 953 } 954#endif 955 956} 957