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