-
Notifications
You must be signed in to change notification settings - Fork 806
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add CoreGraphics.Drawing UT and a bunch of image rendering tests.
This change set introduces DRAW_TEST and DRAW_TEST_F, as well as a handful of image drawing test fixtures. Commands rendered to a test's context as part of its test body will be rasterized and saved in a file named TestImage.$TESTCASE.$TESTNAME.png The test driver's ideal interface is multi-modal; it needs to be able to generate a corpus of reference images on disk, and it needs to be able to diff them. For this, it needs a custom EntryPoint (included), and the ability to parse command-line arguments. The ideal default mode will be one that loads reference images from disk and fails tests if a number of pixels differ. That work is not yet complete. Hopefully, the draw tests here will be able to be plugged into another module that can perhaps render them to screen, or display them as part of a UI-driven flow for comparison and demoing purposes. Refs #1271.
- Loading branch information
Showing
12 changed files
with
1,305 additions
and
10 deletions.
There are no files selected for viewing
262 changes: 262 additions & 0 deletions
262
build/Tests/UnitTests/CoreGraphics.Drawing/CoreGraphics.Drawing.UnitTests.vcxproj
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
149 changes: 149 additions & 0 deletions
149
tests/UnitTests/CoreGraphics.drawing/CGContextDrawingTests.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
//****************************************************************************** | ||
// | ||
// 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" | ||
|
||
DRAW_TEST_F(CGContext, RedBox, UIKitMimicTest) { | ||
CGContextRef context = GetDrawingContext(); | ||
CGRect bounds = GetDrawingBounds(); | ||
|
||
CGContextSetRGBFillColor(context, 1.0, 0.0, 0.0, 1.0); | ||
CGContextFillRect(context, CGRectInset(bounds, 10, 10)); | ||
} | ||
|
||
DRAW_TEST_F(CGContext, FillThenStrokeIsSameAsDrawFillStroke, WhiteBackgroundTest) { | ||
CGContextRef context = GetDrawingContext(); | ||
CGRect bounds = GetDrawingBounds(); | ||
|
||
// Black with a faint red outline will allow us to see through the red. | ||
CGContextSetRGBFillColor(context, 0.0, 0.0, 0.0, 1.0); | ||
CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 0.33); | ||
CGContextSetLineWidth(context, 5.f); | ||
|
||
CGPoint leftCenter{ bounds.size.width / 4.f, bounds.size.height / 2.f }; | ||
CGPoint rightCenter{ 3.f * bounds.size.width / 4.f, bounds.size.height / 2.f }; | ||
|
||
// Left circle, fill then stroke. | ||
CGContextBeginPath(context); | ||
CGContextAddEllipseInRect(context, _CGRectCenteredOnPoint({ 150, 150 }, leftCenter)); | ||
CGContextFillPath(context); | ||
CGContextBeginPath(context); | ||
CGContextAddEllipseInRect(context, _CGRectCenteredOnPoint({ 150, 150 }, leftCenter)); | ||
CGContextStrokePath(context); | ||
|
||
// Right circle, all at once | ||
CGContextBeginPath(context); | ||
CGContextAddEllipseInRect(context, _CGRectCenteredOnPoint({ 150, 150 }, rightCenter)); | ||
CGContextDrawPath(context, kCGPathFillStroke); | ||
} | ||
|
||
static void _drawThreeCirclesInContext(CGContextRef context, CGRect bounds) { | ||
CGPoint center = _CGRectGetCenter(bounds); | ||
CGRect centerEllipseRect = _CGRectCenteredOnPoint({ 150, 150 }, center); | ||
CGFloat translations[]{ -60.f, 0.f, +60.f }; | ||
|
||
for (float xSlide : translations) { | ||
CGRect translatedRect = CGRectApplyAffineTransform(centerEllipseRect, CGAffineTransformMakeTranslation(xSlide, 0)); | ||
CGContextFillEllipseInRect(context, translatedRect); | ||
CGContextStrokeEllipseInRect(context, translatedRect); | ||
} | ||
} | ||
|
||
DRAW_TEST_F(CGContext, OverlappingCirclesColorAlpha, WhiteBackgroundTest) { | ||
CGContextRef context = GetDrawingContext(); | ||
CGRect bounds = GetDrawingBounds(); | ||
|
||
CGContextSetRGBFillColor(context, 1.0, 0.0, 0.0, 0.5); | ||
CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0); | ||
CGContextSetLineWidth(context, 5); | ||
|
||
_drawThreeCirclesInContext(context, bounds); | ||
} | ||
|
||
DRAW_TEST_F(CGContext, OverlappingCirclesGlobalAlpha, WhiteBackgroundTest) { | ||
CGContextRef context = GetDrawingContext(); | ||
CGRect bounds = GetDrawingBounds(); | ||
|
||
CGContextSetRGBFillColor(context, 1.0, 0.0, 0.0, 1.0); | ||
CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0); | ||
CGContextSetLineWidth(context, 5); | ||
|
||
CGContextSetAlpha(context, 0.5); | ||
|
||
_drawThreeCirclesInContext(context, bounds); | ||
} | ||
|
||
DRAW_TEST_F(CGContext, OverlappingCirclesGlobalAlphaStackedWithColorAlpha, WhiteBackgroundTest) { | ||
CGContextRef context = GetDrawingContext(); | ||
CGRect bounds = GetDrawingBounds(); | ||
|
||
CGContextSetRGBFillColor(context, 1.0, 0.0, 0.0, 0.5); | ||
CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0); | ||
CGContextSetLineWidth(context, 5); | ||
|
||
CGContextSetAlpha(context, 0.75); | ||
|
||
_drawThreeCirclesInContext(context, bounds); | ||
} | ||
|
||
DISABLED_DRAW_TEST_F(CGContext, OverlappingCirclesTransparencyLayerAlpha, WhiteBackgroundTest) { | ||
CGContextRef context = GetDrawingContext(); | ||
CGRect bounds = GetDrawingBounds(); | ||
|
||
CGContextSetRGBFillColor(context, 1.0, 0.0, 0.0, 1.0); | ||
CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0); | ||
CGContextSetLineWidth(context, 5); | ||
|
||
CGContextSetAlpha(context, 0.5); | ||
|
||
CGContextBeginTransparencyLayer(context, nullptr); | ||
|
||
_drawThreeCirclesInContext(context, bounds); | ||
|
||
CGContextEndTransparencyLayer(context); | ||
} | ||
|
||
// This test proves that the path is stored fully transformed; | ||
// changing the CTM before stroking it does not cause it to scale! | ||
// However, the stroke width _is_ scaled (!) | ||
DRAW_TEST_F(CGContext, ChangeCTMAfterCreatingPath, WhiteBackgroundTest) { | ||
CGContextRef context = GetDrawingContext(); | ||
CGRect bounds = GetDrawingBounds(); | ||
|
||
CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0); | ||
CGContextSetLineWidth(context, 1); | ||
|
||
CGContextBeginPath(context); | ||
CGContextMoveToPoint(context, 5, 5.5); | ||
CGContextAddLineToPoint(context, (bounds.size.width - 5) / 3, 5.5); | ||
CGContextStrokePath(context); | ||
|
||
CGContextSaveGState(context); | ||
CGContextBeginPath(context); | ||
CGContextMoveToPoint(context, 5, 10.5); | ||
CGContextAddLineToPoint(context, (bounds.size.width - 5) / 3, 10.5); | ||
CGContextScaleCTM(context, 2.0, 2.0); | ||
CGContextStrokePath(context); | ||
CGContextRestoreGState(context); | ||
|
||
CGContextSaveGState(context); | ||
CGContextBeginPath(context); | ||
CGContextMoveToPoint(context, 5, 15.5); | ||
CGContextAddLineToPoint(context, (bounds.size.width - 5) / 3, 15.5); | ||
CGContextScaleCTM(context, 3.0, 3.0); | ||
CGContextStrokePath(context); | ||
CGContextRestoreGState(context); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
//****************************************************************************** | ||
// | ||
// 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" | ||
|
||
#include <CoreFoundation/CoreFoundation.h> | ||
#include <ImageIO/ImageIO.h> | ||
|
||
#include <Starboard/SmartTypes.h> | ||
#include <memory> | ||
|
||
static const CGSize g_defaultCanvasSize{ 512.f, 256.f }; | ||
|
||
woc::unique_cf<CGColorSpaceRef> testing::DrawTest::s_deviceColorSpace; | ||
|
||
void testing::DrawTest::SetUpTestCase() { | ||
s_deviceColorSpace.reset(CGColorSpaceCreateDeviceRGB()); | ||
} | ||
|
||
void testing::DrawTest::TearDownTestCase() { | ||
s_deviceColorSpace.release(); | ||
} | ||
|
||
CGSize testing::DrawTest::CanvasSize() { | ||
return g_defaultCanvasSize; | ||
} | ||
|
||
void testing::DrawTest::SetUp() { | ||
CGSize size = CanvasSize(); | ||
|
||
_context.reset(CGBitmapContextCreate( | ||
nullptr, size.width, size.height, 8, size.width * 4, s_deviceColorSpace.get(), kCGImageAlphaPremultipliedFirst)); | ||
ASSERT_NE(nullptr, _context); | ||
|
||
_bounds = { CGPointZero, size }; | ||
|
||
SetUpContext(); | ||
} | ||
|
||
CFStringRef testing::DrawTest::CreateAdditionalTestDescription() { | ||
return nullptr; | ||
} | ||
|
||
CFStringRef testing::DrawTest::CreateOutputFilename() { | ||
const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); | ||
woc::unique_cf<CFStringRef> additionalDesc{ CreateAdditionalTestDescription() }; | ||
woc::unique_cf<CFStringRef> filename{ CFStringCreateWithFormat(nullptr, | ||
nullptr, | ||
CFSTR("TestImage.%s.%s%s%@.png"), | ||
test_info->test_case_name(), | ||
test_info->name(), | ||
(additionalDesc ? "." : ""), | ||
(additionalDesc ? additionalDesc.get() : CFSTR(""))) }; | ||
return filename.release(); | ||
} | ||
|
||
void testing::DrawTest::TearDown() { | ||
CGContextRef context = GetDrawingContext(); | ||
|
||
woc::unique_cf<CGImageRef> image{ CGBitmapContextCreateImage(context) }; | ||
ASSERT_NE(nullptr, image); | ||
|
||
woc::unique_cf<CFMutableDataRef> imageData{ CFDataCreateMutable(nullptr, 1048576) }; | ||
woc::unique_cf<CGImageDestinationRef> imageDest{ CGImageDestinationCreateWithData(imageData.get(), CFSTR("public.png"), 1, nullptr) }; | ||
CGImageDestinationAddImage(imageDest.get(), image.get(), nullptr); | ||
CGImageDestinationFinalize(imageDest.get()); | ||
|
||
woc::unique_cf<CFStringRef> originalFilename{ CreateOutputFilename() }; | ||
|
||
woc::unique_cf<CFMutableStringRef> filename{ CFStringCreateMutableCopy(nullptr, 0, originalFilename.get()) }; | ||
|
||
CFStringFindAndReplace(filename.get(), CFSTR("DISABLED_"), CFSTR(""), CFRange{ 0, CFStringGetLength(filename.get()) }, 0); | ||
CFStringFindAndReplace(filename.get(), CFSTR("/"), CFSTR("_"), CFRange{ 0, CFStringGetLength(filename.get()) }, 0); | ||
|
||
// This is only populated if CFStringGetCStringPtr fails. | ||
std::unique_ptr<char[]> owningFilenamePtr; | ||
|
||
char* rawFilename = const_cast<char*>(CFStringGetCStringPtr(filename.get(), kCFStringEncodingUTF8)); | ||
size_t len = 0; | ||
|
||
if (!rawFilename) { | ||
CFRange filenameRange{ 0, CFStringGetLength(filename.get()) }; | ||
CFIndex requiredBufferLength = 0; | ||
CFStringGetBytes(filename.get(), filenameRange, kCFStringEncodingUTF8, 0, FALSE, nullptr, 0, &requiredBufferLength); | ||
owningFilenamePtr.reset(new char[requiredBufferLength]); | ||
rawFilename = owningFilenamePtr.get(); | ||
CFStringGetBytes(filename.get(), | ||
filenameRange, | ||
kCFStringEncodingUTF8, | ||
0, | ||
FALSE, | ||
(UInt8*)rawFilename, | ||
requiredBufferLength, | ||
&requiredBufferLength); | ||
len = requiredBufferLength; | ||
} else { | ||
len = strlen(rawFilename); | ||
} | ||
woc::unique_cf<CFURLRef> url{ CFURLCreateFromFileSystemRepresentation(nullptr, (UInt8*)rawFilename, strlen(rawFilename), FALSE) }; | ||
ASSERT_TRUE(CFURLWriteDataAndPropertiesToResource(url.get(), imageData.get(), nullptr, nullptr)); | ||
} | ||
|
||
void testing::DrawTest::SetUpContext() { | ||
// The default context is fine as-is. | ||
} | ||
|
||
void testing::DrawTest::TestBody() { | ||
// Nothing. | ||
} | ||
|
||
CGContextRef testing::DrawTest::GetDrawingContext() { | ||
return _context.get(); | ||
} | ||
|
||
void testing::DrawTest::SetDrawingBounds(CGRect bounds) { | ||
_bounds = bounds; | ||
} | ||
|
||
CGRect testing::DrawTest::GetDrawingBounds() { | ||
return _bounds; | ||
} | ||
|
||
void WhiteBackgroundTest::SetUpContext() { | ||
CGContextRef context = GetDrawingContext(); | ||
CGRect bounds = GetDrawingBounds(); | ||
|
||
CGContextSaveGState(context); | ||
CGContextSetRGBFillColor(context, 1.0, 1.0, 1.0, 1.0); | ||
CGContextFillRect(context, bounds); | ||
CGContextRestoreGState(context); | ||
|
||
CGContextSetRGBStrokeColor(context, 0.0, 0.0, 0.0, 1.0); | ||
} | ||
|
||
CGSize UIKitMimicTest::CanvasSize() { | ||
CGSize parent = WhiteBackgroundTest::CanvasSize(); | ||
return { parent.width * 2., parent.height * 2. }; | ||
} | ||
|
||
void UIKitMimicTest::SetUpContext() { | ||
WhiteBackgroundTest::SetUpContext(); | ||
|
||
CGContextRef context = GetDrawingContext(); | ||
CGRect bounds = GetDrawingBounds(); | ||
|
||
CGContextScaleCTM(context, 1.0, -1.0); | ||
CGContextTranslateCTM(context, 0, -bounds.size.height); | ||
CGContextScaleCTM(context, 2.0, 2.0); | ||
bounds = CGRectApplyAffineTransform(bounds, CGAffineTransformMakeScale(.5, .5)); | ||
|
||
SetDrawingBounds(bounds); | ||
} |
Oops, something went wrong.