CoreGraphics Quartz рисует тень на прозрачном альфа-пути - PullRequest
17 голосов
/ 15 июля 2011

Поиск в Интернете в течение примерно 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);
}

Первое, что я заметил, это только теньвокруг удараНо это не проблема до сих пор.Тень позади пути / прямоугольника все еще видна, что означает: цвет тени влияет на цвет заливки моего пути.Цвет заливки должен быть белым, но вместо этого - серым.Как решить эту проблему?

Ответы [ 4 ]

16 голосов
/ 01 января 2013

Вам придется обрезать контекст и рисовать дважды.

Сначала вы создаете ссылку на свой путь, поскольку вам придется использовать его несколько раз и сохранить свой графический контекст, чтобы вы могли вернуться к нему.

Затем вы обрезаете графический контекст только для рисования за пределами вашего пути. Это делается путем добавления вашего пути к пути, который охватывает весь вид. После того, как вы обрезаете, вы рисуете свой путь тенью, чтобы он рисовался снаружи.

Затем вы восстанавливаете графический контекст до того, каким он был до обрезки, и снова рисуете свой путь без тени.

Это будет выглядеть на оранжевом фоне (белый фон был не очень заметен)

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);
}

Если вы хотите, чтобы вся тень была такой же сильной, и не хотите, чтобы прозрачность цвета заливки делала тень более слабой, вы можете использовать полностью непрозрачный цвет при заливке в первый раз. Он обрезается, так что в любом случае он не будет виден внутри дорожки. Это повлияет только на тень.

12 голосов
/ 04 января 2013

По вашему запросу в комментариях, здесь более глубокое исследование. Рассмотрим следующий снимок экрана (StackOverflow сокращает его для меня - это помогает увидеть его в полном размере.):

5 Different ways of drawing over 3 different kinds of backgrounds

То, что вы видите здесь, - это 5 различных подходов к рисованию (сверху вниз) на трех разных фонах (слева направо). (Я также уменьшил альфа-заливку с 0,8 до 0,5, чтобы облегчить просмотр эффектов.) Три различных подхода к рисованию (сверху вниз):

  1. Только штрих, а не заливка, с тенью
  2. То, как вы разместили код в исходном вопросе
  3. Штрих и заливка без тени
  4. Просто тень сама по себе
  5. Способ, которым @ DavidRönnqvist предложил в своем ответе.

Три разных фона должны быть самоочевидными.

Вы сказали в своем первоначальном вопросе:

Первое, что я заметил, тень только вокруг обводки.

Вот почему я включил первый подход к рисованию. Вот как это на самом деле выглядит, когда есть только штрих, без заливки и (следовательно) только штрих, который затеняется.

Тогда вы сказали:

Но это пока не проблема. Тень позади пути / прямоугольника все еще видимый, что означает: цвет тени влияет на заливку цвет моего пути. Цвет заливки должен быть белым, но вместо этого серым.

Ваш оригинальный код - следующая версия (# 2). Вы видите, что тень для обводки на темнее , чем тень для заливки. Это связано с тем, что альфа цвета обводки равен 1.0, а альфа заливки меньше 1.0. Это может быть легче увидеть в версии № 4, которая представляет собой просто тень - она ​​темнее по краю, где находится штрих. Версия № 3 показывает рисунок без тени. Увидимся, вы можете увидеть красный и изображение полуобнаженного в заливке формы? Таким образом, в исходном чертеже вы видите собственную тень объекта через сам объект.

Если это не имеет смысла, попробуйте подумать о куске стекла, у которого есть оттенок (если вы любите фотографировать, подумайте о фильтре нейтральной плотности ). Если вы удерживаете это стекло между источником света и другой поверхностью, а затем смотрите со стороны и смотрите только на нижнюю поверхность, вы знаете, что полупрозрачное стекло будет отбрасывать некоторую тень, но не такую ​​темную тень, как нечто полностью непрозрачный (как кусок картона). Это то, что вы видите - вы смотрите сквозь объект на его тень.

Версия # 5 - это подход @ DavidRönnqvist. Эффект дурака, о котором я говорил в своем комментарии, легче всего оценить (для меня, во всяком случае), глядя на формы, нарисованные на фоне изображения. То, как он выглядит (в версии № 5), состоит в том, что фигура представляет собой скопированную часть изображения, на которую наложена полупрозрачная белая маска некоторого вида. Если вы оглянетесь на версию №3, то ясно, что в отсутствие тени происходит: вы смотрите через полупрозрачную форму на изображении ниже. Затем, если вы посмотрите на версию # 4, также станет ясно, что у вас есть тень, отбрасываемая объектом, который находится позади вашего глаза / камеры. Оттуда я бы сказал, что при взгляде на версию №2 поверх изображения также ясно, что происходит (даже если оно менее четкое на сплошном цвете). На первый взгляд, мой глаз / мозг не знает, на что он смотрит в версии # 5 - есть момент «визуального диссонанса», прежде чем я устанавливаю мысленную модель «скопированной, маскированной части изображения, плавающей над оригиналом» изображение. "

Так что если этот эффект (# 5) был тем, к чему вы стремились, тогда решение Дэвида будет работать отлично. Я просто хотел отметить, что это своего рода неинтуитивный эффект.

Надеюсь, это полезно. Я поместил полный пример проекта, который использовал для создания этого скриншота, на GitHub .

2 голосов
/ 15 июля 2011
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);
1 голос
/ 01 января 2013
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];
...