CoreGraphics Quartz рисует тень на прозрачном альфа-пути
Поиск в Интернете около 4 часов, не получая ответа так:
Как нарисовать тень на пути с прозрачностью?
- (void)drawRect:(CGRect)rect
{
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(c, 2);
CGContextSetStrokeColorWithColor(c, [[UIColor whiteColor] CGColor]);
CGContextSetShadowWithColor(c, CGSizeMake(0, 5), 5.0, [[UIColor blackColor]CGColor]);
CGContextSetFillColorWithColor(c, [[UIColor colorWithWhite:1.0 alpha:0.8] CGColor]);
// Sample Path
CGContextMoveToPoint(c, 20.0, 10.0);
CGContextAddLineToPoint(c, 100.0, 40.0);
CGContextAddLineToPoint(c, 40.0, 70.0);
CGContextClosePath(c);
CGContextDrawPath(c, kCGPathFillStroke);
}
Первое, что я заметил, тень - только вокруг удара. Но пока это не проблема. Тень за дорожкой/прямоугольником все еще видна, что означает: цвет тени влияет на цвет заливки моего пути. Цвет заливки должен быть белым, а вместо него - серым. Как решить эту проблему?
Ответы
Ответ 1
Вам нужно будет скопировать контекст и сделать два раза.
Сначала вы создаете ссылку на свой путь, так как вам придется использовать его несколько раз и сохранить графический контекст, чтобы вы могли вернуться к нему.
Затем вы привязываете графический контекст к единственной ничьей за пределами вашего пути. Это делается путем добавления вашего пути к пути, который охватывает весь вид. Как только вы обрезаете, вы рисуете свой путь с тенью, чтобы он рисовал снаружи.
Затем вы восстанавливаете графический контекст так, как это было до того, как вы обрезали и снова нарисовали свой путь без тени.
Это будет выглядеть на оранжевом фоне (белый фон был не очень заметен)
![The described drawing on a orange background]()
Код для выполнения приведенного выше рисунка таков:
- (void)drawRect:(CGRect)rect
{
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(c, 2);
CGContextSetStrokeColorWithColor(c, [[UIColor whiteColor] CGColor]);
CGContextSetFillColorWithColor(c, [[UIColor colorWithWhite:1.0 alpha:0.5] CGColor]);
// Sample Path
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, 20.0, 10.0);
CGPathAddLineToPoint(path, NULL, 40.0, 70.0);
CGPathAddLineToPoint(path, NULL, 100.0, 40.0);
CGPathCloseSubpath(path);
// Save the state so we can undo the shadow and clipping later
CGContextSaveGState(c);
{ // Only for readability (so we know what are inside the save/restore scope
CGContextSetShadowWithColor(c, CGSizeMake(0, 5), 5.0, [[UIColor blackColor]CGColor]);
CGFloat width = CGRectGetWidth(self.frame);
CGFloat height = CGRectGetHeight(self.frame);
// Create a mask that covers the entire frame
CGContextMoveToPoint(c, 0, 0);
CGContextAddLineToPoint(c, width, 0);
CGContextAddLineToPoint(c, width, height);
CGContextAddLineToPoint(c, 0, height);
CGContextClosePath(c);
// Add the path (which by even-odd rule will remove it)
CGContextAddPath(c, path);
// Clip to that path (drawing will only happen outside our path)
CGContextClip(c);
// Now draw the path in the clipped context
CGContextAddPath(c, path);
CGContextDrawPath(c, kCGPathFillStroke);
}
CGContextRestoreGState(c); // Go back to before the clipping and before the shadow
// Draw the path without the shadow to get the transparent fill
CGContextAddPath(c, path);
CGContextDrawPath(c, kCGPathFillStroke);
}
Если вы хотите, чтобы вся тень была такой же сильной и не хотела, чтобы прозрачность цвета заливки уменьшала тень, вы можете использовать полностью непрозрачный цвет при заполнении в первый раз. Он будет обрезаться, чтобы он не был виден внутри пути. Это повлияет только на тень.
Ответ 2
По вашему запросу в комментариях, здесь более углубленное исследование. Рассмотрим следующий снимок экрана (StackOverflow сжимает его для меня - он помогает посмотреть его полный размер.):
![5 Different ways of drawing over 3 different kinds of backgrounds]()
То, что вы видите здесь, - это 5 разных подходов к рисованию (сверху вниз) на трех разных фонах (слева направо). (Я также сбросил заполняющую альфа от 0,8 до 0,5, чтобы облегчить просмотр эффектов.) Три разных подхода к рисованию (сверху вниз):
- Просто удар, а не заливка, с тенью
- То, как вы отправили код в исходном вопросе
- Инсульт и заливка без применения тени
- Просто тень, сама по себе
- Путь @DavidRönnqvist предложил в своем ответе.
Три разных фона должны быть самоочевидными.
Вы сказали в своем первоначальном вопросе:
Первое, что я заметил, тень - только вокруг удара.
Вот почему я включил первый подход к рисованию. Это то, что на самом деле выглядит, когда есть только штрих, без заливки, и (поэтому) только штрих затеняется.
Затем вы сказали:
Но это пока не проблема. Тень за дорожкой/прямым все еще видимый, что означает: цвет тени выполняет заливку цвет моего пути. Цвет заливки должен быть белым, а вместо него серым.
Ваш исходный код - это следующая версия (# 2). То, что вы видите, это то, что тень для удара темнее тени для заливки. Это происходит из-за того, что альфа цвета штриха 1.0
, а заполняющая альфа меньше 1.0
. Это может быть проще увидеть в версии №4, которая представляет собой только тень - она темнее вокруг края, где происходит удар. Версия № 3 показывает рисунок без тени. Увидимся, вы можете увидеть красный и изображение, полупризнанные в заполнении формы? Таким образом, в вашем исходном чертеже вы видите объект собственной тени через сам объект.
Если это не имеет смысла, попробуйте подумать о стекле, которое получило оттенок (если вы в фотографии, подумайте о Neutral Density Фильтр). Если вы держите это стекло между источником света и другой поверхностью, а затем заглядываете сбоку и смотрите прямо на нижнюю поверхность, вы знаете, что полупрозрачное стекло собирается наложить какую-то тень, но не так темную тень, как что-то полностью непрозрачный (например, кусок картона). Это то, что вы видите - вы просматриваете объект в тени.
Версия № 5 - это подход @DavidRönnqvist. Ошеломляющий эффект, о котором я говорил в своем комментарии, проще всего оценить (для меня, во всяком случае), посмотрев на фигуры, нарисованные над фоном изображения. То, что в итоге выглядит (в версии № 5), состоит в том, что форма представляет собой ограниченную, скопированную часть изображения, наложенную на полупрозрачную белую маску. Если вы оглянетесь на версию № 3, то ясно, что в отсутствие тени, что происходит: вы просматриваете полупрозрачную форму на изображении внизу. Затем, если вы посмотрите на версию №4, также ясно, что у вас есть тень, которую бросает объект, который находится за вашим глазом/камерой. Оттуда я бы сказал, что это также понятно при просмотре версии № 2 над изображением, что происходит (даже если это менее ясно по сплошному цвету). На первый взгляд, мой глаз/мозг не знает, на что он смотрит в версии №5, - есть момент "визуального диссонанса", прежде чем я установлю ментальную модель "скопированной, замаскированной части изображения, плавающей над оригиналом образ".
Итак, если этот эффект (№5) был тем, чем вы занимались, тогда решение David будет работать отлично. Я просто хотел указать, что это неинтуитивный эффект.
Надеюсь, это полезно. Я поставил полный образец проекта, который я использовал для создания этого скриншота на GitHub.
Ответ 3
CGFloat lineWidth = 2.0f;
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSaveGState(c);
CGContextSetLineWidth(c, lineWidth);
CGContextSetStrokeColorWithColor(c, [[UIColor whiteColor] CGColor]);
CGContextSetShadowWithColor(c, CGSizeMake(0, 5), 5.0, [[UIColor blackColor]CGColor]);
CGContextAddRect(c, someRect);
CGContextDrawPath(c, kCGPathStroke);
CGContextRestoreGState(c);
someRect.origin.x += lineWidth/2;
someRect.origin.y += lineWidth/2;
someRect.size.width -= lineWidth;
someRect.size.height -= lineWidth;
CGContextClearRect(c, someRect);
CGContextSetFillColorWithColor(c, [[[UIColor whiteColor] colorWithAlphaComponent:0.8] CGColor]);
CGContextAddRect(c, someRect);
CGContextDrawPath(c, kCGPathFill);
Ответ 4
NSShadow* shadow = [[NSShadow alloc] init];
[shadow setShadowColor: [NSColor blackColor]];
[shadow setShadowOffset: NSMakeSize(2.1, -3.1)];
[shadow setShadowBlurRadius: 5];
NSBezierPath* bezierPath = [NSBezierPath bezierPath];
[bezierPath moveToPoint: NSMakePoint(12.5, 6.5)];
[bezierPath curveToPoint: NSMakePoint(52.5, 8.5) controlPoint1: NSMakePoint(40.5, 13.5) controlPoint2: NSMakePoint(52.5, 8.5)];
[bezierPath lineToPoint: NSMakePoint(115.5, 13.5)];
[bezierPath lineToPoint: NSMakePoint(150.5, 6.5)];
[bezierPath lineToPoint: NSMakePoint(201.5, 13.5)];
[bezierPath lineToPoint: NSMakePoint(222.5, 8.5)];
[NSGraphicsContext saveGraphicsState];
[shadow set];
[[NSColor blackColor] setStroke];
[bezierPath setLineWidth: 1];
[bezierPath stroke];
[NSGraphicsContext restoreGraphicsState];