Skip to content

Commit

Permalink
Implementation of Linear gradients with full optional support (#1416)
Browse files Browse the repository at this point in the history
  • Loading branch information
msft-Jeyaram authored Dec 21, 2016
1 parent e579967 commit 0c1e282
Show file tree
Hide file tree
Showing 18 changed files with 336 additions and 8 deletions.
88 changes: 86 additions & 2 deletions Frameworks/CoreGraphics/CGContext.mm
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#import "CGPathInternal.h"
#import "CGIWICBitmap.h"
#import "CGPatternInternal.h"
#import "CGGradientInternal.h"

#import <CFCppBase.h>

Expand All @@ -49,6 +50,9 @@

static const wchar_t* TAG = L"CGContext";

// Coordinate offset to support CGGradientDrawingOptions
static const float s_kCGGradientOffsetPoint = 1E-45;

enum _CGCoordinateMode : unsigned int { _kCGCoordinateModeDeviceSpace = 0, _kCGCoordinateModeUserSpace };

// A drawing context is represented by a number of layers, each with their own drawing state:
Expand Down Expand Up @@ -2356,14 +2360,94 @@ void CGContextDrawTiledImage(CGContextRef context, CGRect rect, CGImageRef image

#pragma region Drawing Operations - Gradient + Shading
/**
@Status Stub
* Insert a transparent color at the specified 'location'.
* This will also move the color at the specified 'location' to the supplied 'position'
*/
static inline void __CGGradientInsertTransparentColor(std::vector<D2D1_GRADIENT_STOP>& gradientStops, int location, float position) {
gradientStops[location].position = position;
// set the edge location to be transparent
D2D1_GRADIENT_STOP transparent = { location, D2D1::ColorF(0, 0, 0, 0) };
gradientStops.push_back(transparent);
}

/*
* Convert CGGradient to D2D1_GRADIENT_STOP
*/
static std::vector<D2D1_GRADIENT_STOP> __CGGradientToD2D1GradientStop(CGContextRef context,
CGGradientRef gradient,
CGGradientDrawingOptions options) {
unsigned long gradientCount = _CGGradientGetCount(gradient);
std::vector<D2D1_GRADIENT_STOP> gradientStops(gradientCount);

CGFloat* colorComponents = _CGGradientGetColorComponents(gradient);
CGFloat* locations = _CGGradientGetStopLocation(gradient);
for (unsigned long i = 0; i < gradientCount; ++i) {
// TODO #1541: The indexing needs to get updated based on colorspace (for non RGBA)
unsigned int colorIndex = (i * 4);
gradientStops[i].color = D2D1::ColorF(colorComponents[colorIndex],
colorComponents[colorIndex + 1],
colorComponents[colorIndex + 2],
colorComponents[colorIndex + 3]);
gradientStops[i].position = locations[i];
}

// we want to support CGGradientDrawingOptions, but by default d2d will extend the region via repeating the brush (or other
// effect based on the extend mode). We support that by inserting a point (with transparent color) close to the start/end points, such
// that d2d will automatically extend the transparent color, thus we obtain the desired effect for CGGradientDrawingOptions.

if (!(options & kCGGradientDrawsBeforeStartLocation)) {
__CGGradientInsertTransparentColor(gradientStops, 0, s_kCGGradientOffsetPoint);
}

if (!(options & kCGGradientDrawsAfterEndLocation)) {
__CGGradientInsertTransparentColor(gradientStops, 1, 1.f - s_kCGGradientOffsetPoint);
}

return gradientStops;
}
/**
@Status Interoperable
*/
void CGContextDrawLinearGradient(
CGContextRef context, CGGradientRef gradient, CGPoint startPoint, CGPoint endPoint, CGGradientDrawingOptions options) {
NOISY_RETURN_IF_NULL(context);
NOISY_RETURN_IF_NULL(gradient);
RETURN_IF(!context->ShouldDraw());

UNIMPLEMENTED();
RETURN_IF(_CGGradientGetCount(gradient) == 0);

std::vector<D2D1_GRADIENT_STOP> gradientStops = __CGGradientToD2D1GradientStop(context, gradient, options);

ComPtr<ID2D1GradientStopCollection> gradientStopCollection;

ComPtr<ID2D1DeviceContext> deviceContext = context->DeviceContext();
FAIL_FAST_IF_FAILED(deviceContext->CreateGradientStopCollection(gradientStops.data(),
gradientStops.size(),
D2D1_GAMMA_2_2,
D2D1_EXTEND_MODE_CLAMP,
&gradientStopCollection));

ComPtr<ID2D1LinearGradientBrush> linearGradientBrush;
FAIL_FAST_IF_FAILED(deviceContext->CreateLinearGradientBrush(
D2D1::LinearGradientBrushProperties(_CGPointToD2D_F(startPoint), _CGPointToD2D_F(endPoint)),
D2D1::BrushProperties(context->CurrentGState().alpha,
__CGAffineTransformToD2D_F(CGContextGetUserSpaceToDeviceSpaceTransform(context))),
gradientStopCollection.Get(),
&linearGradientBrush));

// Area to fill
D2D1_SIZE_F targetSize = deviceContext->GetSize();
D2D1_RECT_F region = D2D1::RectF(0, 0, targetSize.width, targetSize.height);

ComPtr<ID2D1CommandList> commandList;
FAIL_FAST_IF_FAILED(context->DrawToCommandList(_kCGCoordinateModeDeviceSpace,
nullptr,
&commandList,
[&](CGContextRef context, ID2D1DeviceContext* deviceContext) {
deviceContext->FillRectangle(&region, linearGradientBrush.Get());
return S_OK;
}));
FAIL_FAST_IF_FAILED(context->DrawImage(commandList.Get()));
}

/**
Expand Down
24 changes: 22 additions & 2 deletions Frameworks/CoreGraphics/CGGradient.mm
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//******************************************************************************
//
// Copyright (c) 2016 Intel Corporation. All rights reserved.
// Copyright (c) 2016 Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft. All rights reserved.
//
// This code is licensed under the MIT License (MIT).
//
Expand Down Expand Up @@ -36,7 +36,7 @@ - (void)dealloc {
@end

__CGGradient::__CGGradient() : _components(NULL), _locations(NULL) {
object_setClass((id) this, [CGNSGradient class]);
object_setClass((id)this, [CGNSGradient class]);
}

__CGGradient::~__CGGradient() {
Expand Down Expand Up @@ -181,3 +181,23 @@ CFTypeID CGGradientGetTypeID() {
UNIMPLEMENTED();
return StubReturn();
}

CGFloat* _CGGradientGetStopLocation(CGGradientRef gradient) {
RETURN_NULL_IF(!gradient);
return gradient->_locations;
}

CGFloat* _CGGradientGetColorComponents(CGGradientRef gradient) {
RETURN_NULL_IF(!gradient);
return gradient->_components;
}

unsigned long _CGGradientGetCount(CGGradientRef gradient) {
RETURN_RESULT_IF_NULL(gradient, 0);
return gradient->_count;
}

CGColorSpaceModel _CGGradientGetColorSpaceModel(CGGradientRef gradient) {
RETURN_RESULT_IF_NULL(gradient, kCGColorSpaceModelRGB);
return gradient->_colorSpaceModel;
}
14 changes: 11 additions & 3 deletions Frameworks/include/CGGradientInternal.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//******************************************************************************
//
// Copyright (c) 2016 Intel Corporation. All rights reserved.
// Copyright (c) 2015 Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft. All rights reserved.
//
// This code is licensed under the MIT License (MIT).
//
Expand All @@ -17,8 +17,8 @@

#pragma once

#include "CoreGraphics/CGGradient.h"
#include "CoreGraphicsInternal.h"
#import <CoreGraphics/CGGradient.h>
#import "CoreGraphicsInternal.h"
#include <objc/runtime.h>

class __CGGradient : private objc_object {
Expand All @@ -34,3 +34,11 @@ class __CGGradient : private objc_object {
void initWithColorComponents(const float* components, const float* locations, size_t count, CGColorSpaceRef colorspace);
void initWithColors(CFArrayRef components, const float* locations, CGColorSpaceRef colorspace);
};

unsigned long _CGGradientGetCount(CGGradientRef gradient);

CGFloat* _CGGradientGetStopLocation(CGGradientRef gradient);

CGFloat* _CGGradientGetColorComponents(CGGradientRef gradient);

CGColorSpaceModel _CGGradientGetColorSpaceModel(CGGradientRef gradient);
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@
<ClangCompile Include="$(StarboardBasePath)\tests\unittests\CoreGraphics.Drawing\CGContextDrawing_FillTests.cpp" />
<ClangCompile Include="$(StarboardBasePath)\tests\unittests\CoreGraphics.Drawing\CGContextDrawing_ShadowTests.cpp" />
<ClangCompile Include="$(StarboardBasePath)\tests\unittests\CoreGraphics.Drawing\CGContextDrawing_ImageMaskTests.cpp" />
<ClangCompile Include="$(StarboardBasePath)\tests\unittests\CoreGraphics.Drawing\CGContextDrawing_GradientTests.cpp" />
<ClangCompile Include="$(StarboardBasePath)\tests\unittests\CoreGraphics.Drawing\CGPathDrawingTests.cpp" />
<ClangCompile Include="$(StarboardBasePath)\tests\unittests\CoreGraphics.Drawing\DrawingTest.cpp" />
<ClangCompile Include="$(StarboardBasePath)\tests\unittests\CoreGraphics.drawing\ImageComparison.cpp" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -588,4 +588,4 @@ DRAW_TEST(CGContext, PremultipliedAlphaImage) {

CGContextSetRGBFillColor(context, 1.0, 0.0, 0.0, 0.5);
CGContextFillRect(context, { 0, 0, 100, 100 });
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
//******************************************************************************
//
// Copyright (c) Microsoft. All rights reserved.
//
// This code is licensed under the MIT License (MIT).
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
//******************************************************************************

#include "DrawingTest.h"

#pragma region LinearGradient

static void _drawLinearGradient(CGContextRef context,
CGPoint startPoint,
CGPoint endPoint,
CGFloat components[],
CGFloat locations[],
CGGradientDrawingOptions options) {
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient =
CGGradientCreateWithColorComponents(colorspace, components, locations, std::extent<decltype(locations)>::value);

CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, options);
CFRelease(colorspace);
CFRelease(gradient);
}

static void _drawShortLinearGradientWithOptions(CGContextRef context, CGRect bounds, CGGradientDrawingOptions option) {
CGFloat locations[2] = { 0, 1 };
CGFloat components[8] = { 0.0, 1, 0.0, 1.0, 1.0, 0, 0, 1.0 };

CGRect borderRect = CGRectInset(bounds, 30, 50);

_drawLinearGradient(context,
borderRect.origin,
CGPointMake(borderRect.size.width, borderRect.size.height),
components,
locations,
option);
}

DISABLED_DRAW_TEST_F(CGGradient, LinearGradient, UIKitMimicTest) {
CGFloat locations[2] = { 0, 1 };
CGFloat components[8] = { 0.0, 0.0, 1, 1.0, 1.0, 0, 0, 1.0 };
_drawLinearGradient(GetDrawingContext(),
CGPointMake(0, 0),
CGPointMake(512, 1024),
components,
locations,
kCGGradientDrawsBeforeStartLocation);
}

DISABLED_DRAW_TEST_F(CGGradient, LinearGradientShortBothSides_Options_0, UIKitMimicTest) {
_drawShortLinearGradientWithOptions(GetDrawingContext(), GetDrawingBounds(), 0);
}

DISABLED_DRAW_TEST_F(CGGradient, LinearGradientShortBothSides_Options_kCGGradientDrawsBeforeStartLocation, UIKitMimicTest) {
_drawShortLinearGradientWithOptions(GetDrawingContext(), GetDrawingBounds(), kCGGradientDrawsBeforeStartLocation);
}

DISABLED_DRAW_TEST_F(CGGradient, LinearGradientShortBothSides_Options_kCGGradientDrawsAfterEndLocation, UIKitMimicTest) {
_drawShortLinearGradientWithOptions(GetDrawingContext(), GetDrawingBounds(), kCGGradientDrawsAfterEndLocation);
}

DRAW_TEST_F(CGGradient, LinearGradientInvalidCount, UIKitMimicTest) {
CGFloat locations[] = { 0.0 };

CGFloat components[] = {
0.85, 0, 0, 1.0,
};

CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorspace, components, locations, 0);

CGContextDrawLinearGradient(GetDrawingContext(),
gradient,
CGPointMake(0, 0),
CGPointMake(512, 1024),
kCGGradientDrawsBeforeStartLocation);
CFRelease(colorspace);
CFRelease(gradient);
}

DISABLED_DRAW_TEST_F(CGGradient, LinearGradient2, UIKitMimicTest) {
CGFloat locations[] = { 0.0, 0.33, 0.66, 1.0 };

CGFloat components[] = {
0.85, 0, 0, 1.0, 1, 0, 0, 1.0, 0.85, 0.3, 0, 1.0, 0.1, 0, 0.9, 1.0,
};

_drawLinearGradient(GetDrawingContext(),
CGPointMake(0, 0),
CGPointMake(512, 1024),
components,
locations,
kCGGradientDrawsBeforeStartLocation);
}

DISABLED_DRAW_TEST_F(CGGradient, LinearGradient2Short, UIKitMimicTest) {
CGFloat locations[] = { 0.0, 0.33, 1.0 };

CGFloat components[] = { 0.85, 0, 0, 1.0, 1, 0, 0, 1.0, 0.85, 0.3, 0, 1.0 };
_drawLinearGradient(GetDrawingContext(),
CGPointMake(120, 200),
CGPointMake(350, 800),
components,
locations,
kCGGradientDrawsBeforeStartLocation);
}

DISABLED_DRAW_TEST_F(CGGradient, LinearGradient3, UIKitMimicTest) {
CGFloat locations[] = { 0.0, 0.5, 1 };

CGFloat components[] = {
1, 0, 0, 1.0, 0, 1, 0, 1.0, 0, 0, 1, 1.0,
};
_drawLinearGradient(GetDrawingContext(),
CGPointMake(0, 0),
CGPointMake(512, 1024),
components,
locations,
kCGGradientDrawsBeforeStartLocation);
}

DISABLED_DRAW_TEST_F(CGGradient, LinearGradientWithLowOpacity, UIKitMimicTest) {
CGFloat locations[] = { 0.0, 0.5, 1 };

CGFloat components[] = {
1, 0, 0, 0.1, 0, 1, 0, 0.9, 0, 0, 1, 0.8,
};

_drawLinearGradient(GetDrawingContext(),
CGPointMake(300, 750),
CGPointMake(0, 0),
components,
locations,
kCGGradientDrawsBeforeStartLocation);
}

DISABLED_DRAW_TEST_F(CGGradient, LinearGradientWithAlpha, UIKitMimicTest) {
CGFloat locations[] = { 0.0, 0.25, 0.5, 0.6, 0.8, 0.9, 1 };

CGFloat components[] = {
1, 0, 0, 1, 0.4, 0.1, 0.5, 1, 0.50, 0.2, 0.99, 1, 0.41, 0.56, 0, 1, 0.12, 0.12, .3, 1, 0.9, 0.4, 1, 1, 0.2, 0.3, 0.8, 1,
};

CGContextSetAlpha(GetDrawingContext(), 0.75);
_drawLinearGradient(GetDrawingContext(),
CGPointMake(0, 0),
CGPointMake(512, 1024),
components,
locations,
kCGGradientDrawsBeforeStartLocation);
}

DISABLED_DRAW_TEST_F(CGGradient, LinearGradientWithLowOpacityShort, UIKitMimicTest) {
CGFloat locations[] = { 0.0, 0.5, 1 };

CGFloat components[] = {
1, 0, 0, 0.8, 0, 1, 0, 0.9, 0, 0, 1, 0.1,
};

_drawLinearGradient(GetDrawingContext(),
CGPointMake(250, 300),
CGPointMake(0, 0),
components,
locations,
kCGGradientDrawsAfterEndLocation);
}

#pragma endregion LinearGradient
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 0c1e282

Please sign in to comment.