Skip to content

Commit

Permalink
Implementation of Tiled Images + tests #1417
Browse files Browse the repository at this point in the history
  • Loading branch information
msft-Jeyaram committed Dec 14, 2016
1 parent 497eb97 commit 769d982
Show file tree
Hide file tree
Showing 30 changed files with 300 additions and 19 deletions.
94 changes: 76 additions & 18 deletions Frameworks/CoreGraphics/CGContext.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1950,30 +1950,43 @@ void CGContextEOFillPath(CGContextRef context) {

#pragma region Drawing Operations - CGImage

/**
@Status Interoperable
*/
void CGContextDrawImage(CGContextRef context, CGRect rect, CGImageRef image) {
NOISY_RETURN_IF_NULL(context);
NOISY_RETURN_IF_NULL(image);
static HRESULT __CreateD2DBitmapFromCGImage(CGContextRef context, CGImageRef image, ID2D1Bitmap** bitmap) {
RETURN_HR_IF_NULL(E_INVALIDARG, context);
RETURN_HR_IF_NULL(E_INVALIDARG, image);
RETURN_HR_IF_NULL(E_POINTER, bitmap);

// Obtain the pixel format for the image
WICPixelFormatGUID imagePixelFormat = _CGImageGetWICPixelFormat(image);
ComPtr<IWICBitmap> bmap;
RETURN_IF_FAILED(_CGImageGetWICImageSource(image, &bmap));

CFRetain(image);
woc::unique_cf<CGImageRef> refImage;
refImage.reset(image);
ComPtr<ID2D1Bitmap> d2dBitmap;
RETURN_IF_FAILED(context->DeviceContext()->CreateBitmapFromWicBitmap(bmap.Get(), nullptr, &d2dBitmap));
*bitmap = d2dBitmap.Detach();
return S_OK;
}

static CGImageRef __CGContextCreateRenderableImage(CGImageRef image) {
RETURN_NULL_IF(!image);
WICPixelFormatGUID imagePixelFormat = _CGImageGetWICPixelFormat(image);
if (!_CGIsValidRenderTargetPixelFormat(imagePixelFormat)) {
// convert it to a valid pixelformat
refImage.reset(_CGImageCreateCopyWithPixelFormat(image, GUID_WICPixelFormat32bppPBGRA));
return _CGImageCreateCopyWithPixelFormat(image, GUID_WICPixelFormat32bppPBGRA);
}

ComPtr<IWICBitmap> bmap;
FAIL_FAST_IF_FAILED(_CGImageGetWICImageSource(refImage.get(), &bmap));
CGImageRetain(image);
return image;
}

/**
@Status Interoperable
*/
void CGContextDrawImage(CGContextRef context, CGRect rect, CGImageRef image) {
NOISY_RETURN_IF_NULL(context);
NOISY_RETURN_IF_NULL(image);

woc::unique_cf<CGImageRef> refImage{ __CGContextCreateRenderableImage(image) };

ComPtr<ID2D1Bitmap> d2dBitmap;
FAIL_FAST_IF_FAILED(context->DeviceContext()->CreateBitmapFromWicBitmap(bmap.Get(), nullptr, &d2dBitmap));
FAIL_FAST_IF_FAILED(__CreateD2DBitmapFromCGImage(context, refImage.get(), &d2dBitmap));

// Flip the image to account for change in coordinate system origin.
CGAffineTransform flipImage = CGAffineTransformMakeTranslation(rect.origin.x, rect.origin.y + (rect.size.height / 2.0));
Expand Down Expand Up @@ -2022,13 +2035,58 @@ void _CGContextDrawImageRect(CGContextRef context, CGImageRef image, CGRect src,
}

/**
@Status Stub
@Status Interoperable
*/
void CGContextDrawTiledImage(CGContextRef context, CGRect rect, CGImageRef image) {
// TODO #1417: This can be combined with brushes (brush + tiled fill);
NOISY_RETURN_IF_NULL(context);
UNIMPLEMENTED();
NOISY_RETURN_IF_NULL(image);

woc::unique_cf<CGImageRef> refImage{ __CGContextCreateRenderableImage(image) };

ComPtr<ID2D1Bitmap> d2dBitmap;
FAIL_FAST_IF_FAILED(__CreateD2DBitmapFromCGImage(context, refImage.get(), &d2dBitmap));

ComPtr<ID2D1BitmapBrush> bitmapBrush;
ComPtr<ID2D1DeviceContext> deviceContext = context->DeviceContext();
FAIL_FAST_IF_FAILED(deviceContext->CreateBitmapBrush(d2dBitmap.Get(), &bitmapBrush));

// set the bitmap properties for the brush to be repeated
bitmapBrush->SetExtendModeX(D2D1_EXTEND_MODE_WRAP);
bitmapBrush->SetExtendModeY(D2D1_EXTEND_MODE_WRAP);

// |1 0 0| is the transformation matrix for flipping a rect about its Y midpoint m. (m = (y + h/2))
// |0 -1 0|
// |0 2m 1|
//
// Combined with [scale sx * sy] * [translate X, Y], that becomes:
// |sx 0 0|
// | 0 -sy 0|
// | x -y+2m 0|
// Or, the transformation matrix for drawing a flipped rect at a scale and offset.
D2D1_SIZE_U bitmapSize = d2dBitmap->GetPixelSize();
CGFloat sx = rect.size.width / bitmapSize.width;
CGFloat sy = rect.size.height / bitmapSize.height;
CGFloat m = rect.origin.y + (rect.size.height / 2.f);

CGAffineTransform transform{ sx, 0, 0, -sy, rect.origin.x, (2 * m) - rect.origin.y };
transform = CGAffineTransformConcat(transform, CGContextGetUserSpaceToDeviceSpaceTransform(context));

This comment has been minimized.

Copy link
@DHowett-MSFT

DHowett-MSFT Dec 15, 2016

Whichever of us merges second will be tasked with lifting this out into a common function ;)


// set the transform for the brush and alpha
bitmapBrush->SetTransform(__CGAffineTransformToD2D_F(transform));
bitmapBrush->SetOpacity(context->CurrentGState().alpha);

// set the interpolationMode
bitmapBrush->SetInterpolationMode(D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR);

This comment has been minimized.

Copy link
@DHowett-MSFT

DHowett-MSFT Dec 15, 2016

You must use the context's GState's bitmap interpolation mode, but only if the image is shouldInterpolate.


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

deviceContext->BeginDraw();

This comment has been minimized.

Copy link
@DHowett-MSFT

DHowett-MSFT Dec 15, 2016

!no!

Please run this through DrawToCommandList + DrawImage.
Otherwise, tiled images will not be clipped, shadowed or blended with the context's existing contents.

This comment has been minimized.

Copy link
@DHowett-MSFT

DHowett-MSFT Dec 15, 2016

Also, the rect will not be transformed.
When you draw through command list, please pass _kCGCoordinateModeUserSpace.

This comment has been minimized.

Copy link
@DHowett-MSFT

DHowett-MSFT Dec 15, 2016

Oh, I see. Pass in the Device space coordinate mode. However, you still have to run through the stack to get clipping and masking!

deviceContext->FillRectangle(&region, bitmapBrush.Get());
FAIL_FAST_IF_FAILED(deviceContext->EndDraw());
}

#pragma endregion

#pragma region Drawing Operations - Gradient + Shading
Expand Down
141 changes: 140 additions & 1 deletion tests/UnitTests/CoreGraphics.drawing/CGContextDrawingTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,146 @@ DISABLED_DRAW_TEST_F(CGContext, DrawIntoRect, UIKitMimicTest) {
}
#endif

static void _drawTiledImage(CGContextRef context, CGRect rect, const std::string& name) {
auto drawingConfig = DrawingTestConfig::Get();
woc::unique_cf<CFStringRef> testFilename{ _CFStringCreateWithStdString(drawingConfig->GetResourcePath(name)) };
woc::unique_cf<CGImageRef> image{ _CGImageCreateFromPNGFile(testFilename.get()) };
ASSERT_NE(image, nullptr);
CGContextDrawTiledImage(context, rect, image.get());
}

DRAW_TEST_F(CGContext, TiledImageHeart, UIKitMimicTest) {
CGRect rect = { { 0, 0 }, { 128, 128 } };
_drawTiledImage(GetDrawingContext(), rect, "tiledImageHeart.png");
}

DRAW_TEST_F(CGContext, TiledImageHeartScaledUp, UIKitMimicTest) {
CGRect rect = { { 0, 0 }, { 250, 250 } };
_drawTiledImage(GetDrawingContext(), rect, "tiledImageHeart.png");
}

DRAW_TEST_F(CGContext, TiledImageHeartScaledTiny, UIKitMimicTest) {
CGRect rect = { { 0, 0 }, { 1, 1 } };
_drawTiledImage(GetDrawingContext(), rect, "tiledImageHeart.png");
}

DRAW_TEST_F(CGContext, TiledImageHeartScaledAlpha1, UIKitMimicTest) {
CGRect rect = { { 0, 0 }, { 100, 100 } };
CGContextSetAlpha(GetDrawingContext(), 0.8);
_drawTiledImage(GetDrawingContext(), rect, "tiledImageHeart.png");
}

DRAW_TEST_F(CGContext, TiledImageHeartScaledAlpha2, UIKitMimicTest) {
CGRect rect = { { 0, 0 }, { 256, 256 } };
CGContextSetAlpha(GetDrawingContext(), 0.24);
_drawTiledImage(GetDrawingContext(), rect, "tiledImageHeart.png");
}

DRAW_TEST_F(CGContext, TiledImageHeartScaledAlpha3, UIKitMimicTest) {
CGRect rect = { { 0, 0 }, { 300, 513 } };
CGContextSetAlpha(GetDrawingContext(), 0.66);
_drawTiledImage(GetDrawingContext(), rect, "tiledImageHeart.png");
}

DRAW_TEST_F(CGContext, TiledImageHeartScaledDown, UIKitMimicTest) {
CGRect rect = { { 0, 0 }, { 50, 50 } };
_drawTiledImage(GetDrawingContext(), rect, "tiledImageHeart.png");
}

DRAW_TEST_F(CGContext, TiledImageHeartScaled, UIKitMimicTest) {
CGRect rect = { { 0, 0 }, { 250, 128 } };
_drawTiledImage(GetDrawingContext(), rect, "tiledImageHeart.png");
}

DRAW_TEST_F(CGContext, TiledImageDog, UIKitMimicTest) {
CGRect rect = { { 0, 0 }, { 256, 256 } };
_drawTiledImage(GetDrawingContext(), rect, "tiledImageDog.png");
}

DRAW_TEST_F(CGContext, TiledImageDogScaledDown, UIKitMimicTest) {
CGRect rect = { { 0, 0 }, { 50, 50 } };
_drawTiledImage(GetDrawingContext(), rect, "tiledImageDog.png");
}

DRAW_TEST_F(CGContext, TiledImageDogScaledUp, UIKitMimicTest) {
CGRect rect = { { 0, 0 }, { 512, 512 } };
_drawTiledImage(GetDrawingContext(), rect, "tiledImageDog.png");
}

DRAW_TEST_F(CGContext, TiledImageDogScaled, UIKitMimicTest) {
CGRect rect = { { 0, 0 }, { 350, 500 } };
_drawTiledImage(GetDrawingContext(), rect, "tiledImageDog.png");
}

DRAW_TEST_F(CGContext, TiledImageDogScaled2, UIKitMimicTest) {
CGRect rect = { { 0, 0 }, { 128, 240 } };
_drawTiledImage(GetDrawingContext(), rect, "tiledImageDog.png");
}

DRAW_TEST_F(CGContext, TiledImageDogScaledAspectRatioWrong, UIKitMimicTest) {
CGRect rect = { { 0, 0 }, { 1024, 25 } };
_drawTiledImage(GetDrawingContext(), rect, "tiledImageDog.png");
}

DRAW_TEST_F(CGContext, TiledImageDogScaledAspectRatio, UIKitMimicTest) {
CGRect rect = { { 0, 0 }, { 1024, 1024 } };
_drawTiledImage(GetDrawingContext(), rect, "tiledImageDog.png");
}

DRAW_TEST_F(CGContext, TiledImageDogScaledAlpha, UIKitMimicTest) {
CGRect rect = { { 0, 0 }, { 100, 100 } };
CGContextSetAlpha(GetDrawingContext(), 0.8);
_drawTiledImage(GetDrawingContext(), rect, "tiledImageDog.png");
}

DRAW_TEST_F(CGContext, TiledImageDogScaledAlpha2, UIKitMimicTest) {
CGRect rect = { { 0, 0 }, { 256, 256 } };
CGContextSetAlpha(GetDrawingContext(), 0.24);
_drawTiledImage(GetDrawingContext(), rect, "tiledImageDog.png");
}

DRAW_TEST_F(CGContext, TiledImageDogScaledAlpha3, UIKitMimicTest) {
CGRect rect = { { 0, 0 }, { 300, 513 } };
CGContextSetAlpha(GetDrawingContext(), 0.66);
_drawTiledImage(GetDrawingContext(), rect, "tiledImageDog.png");
}

DRAW_TEST_F(CGContext, TiledImageCustom, UIKitMimicTest) {
CGRect rect = { { 0, 0 }, { 562, 469 } };
_drawTiledImage(GetDrawingContext(), rect, "tiledImageCircleMe.png");
}

DRAW_TEST_F(CGContext, TiledImageCustomScaledUp, UIKitMimicTest) {
CGRect rect = { { 0, 0 }, { 2050, 2050 } };
_drawTiledImage(GetDrawingContext(), rect, "tiledImageCircleMe.png");
}

DRAW_TEST_F(CGContext, TiledImageCustomScaledDown, UIKitMimicTest) {
CGRect rect = { { 0, 0 }, { 20, 20 } };
_drawTiledImage(GetDrawingContext(), rect, "tiledImageCircleMe.png");
}

DRAW_TEST_F(CGContext, TiledImageCustomScaledDownReallyLow, UIKitMimicTest) {
CGRect rect = { { 0, 0 }, { 1, 1 } };
_drawTiledImage(GetDrawingContext(), rect, "tiledImageCircleMe.png");
}

DRAW_TEST_F(CGContext, TiledImageCustomScaled, UIKitMimicTest) {
CGRect rect = { { 0, 0 }, { 10, 250 } };
_drawTiledImage(GetDrawingContext(), rect, "tiledImageCircleMe.png");
}

DRAW_TEST_F(CGContext, TiledImageCustomScaledObscure, UIKitMimicTest) {
CGRect rect = { { 0, 0 }, { 253, 13 } };
_drawTiledImage(GetDrawingContext(), rect, "tiledImageCircleMe.png");
}

DRAW_TEST_F(CGContext, TiledImageCustomScaledAlpha, UIKitMimicTest) {

This comment has been minimized.

Copy link
@DHowett-MSFT

DHowett-MSFT Dec 15, 2016

These are perfect for Parameterized tests.

If you do that (and please do), you can also add transformations.

Look at blend modes and the GoogleTest Advanced Guide for info on creating parameterized tests!

CGRect rect = { { 0, 0 }, { 128, 128 } };
CGContextSetAlpha(GetDrawingContext(), 0.88);
_drawTiledImage(GetDrawingContext(), rect, "tiledImageCircleMe.png");
}

DISABLED_DRAW_TEST_F(CGContext, DrawAnImage, UIKitMimicTest) {
// Load an Image and draw it into the canvas context
auto drawingConfig = DrawingTestConfig::Get();
Expand Down Expand Up @@ -98,7 +238,6 @@ DISABLED_DRAW_TEST_F(CGContext, DrawAContextIntoAnImage, UIKitMimicTest) {
CGContextDrawImage(context, bounds, image.get());
}


DISABLED_DRAW_TEST_F(CGContext, FillThenStrokeIsSameAsDrawFillStroke, WhiteBackgroundTest) {
CGContextRef context = GetDrawingContext();
CGRect bounds = GetDrawingBounds();
Expand Down
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
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
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
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
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
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
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
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
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions tests/unittests/CoreGraphics.drawing/data/tiledImageDog.png
3 changes: 3 additions & 0 deletions tests/unittests/CoreGraphics.drawing/data/tiledImageHeart.png

0 comments on commit 769d982

Please sign in to comment.