Создание прозрачного градиента и использование его в качестве альфа-маски в SpriteKit

Я пытаюсь сделать градиент и использовать его как альфа-маску. Прямо сейчас я могу сделать изображение, подобное этому (от черного до прозрачного):

градиент

Это код, который я использую, чтобы сделать все это:

private func createImage(width: CGFloat, height: CGFloat) -> CGImageRef?{

        if let ciFilter = CIFilter(name: "CILinearGradient"){

            let ciContext = CIContext()

            ciFilter.setDefaults()

            let startColor  = CIColor(red: 0, green: 0, blue: 0, alpha: 0)
            let endColor    = CIColor(red: 0, green: 0, blue: 0, alpha: 1)

            let startVector = CIVector(x: 0, y: height-10)
            let endVector   = CIVector(x: 0, y: height-22)

            ciFilter.setValue(startColor,   forKey: "inputColor0")
            ciFilter.setValue(endColor,     forKey: "inputColor1")
            ciFilter.setValue(startVector,  forKey: "inputPoint0")
            ciFilter.setValue(endVector,    forKey: "inputPoint1")



            if let outputImage = ciFilter.outputImage {

                let  cgImage:CGImageRef = ciContext.createCGImage(outputImage, fromRect: CGRect(x: 0, y: 0, width: width, height: height))

                return  cgImage

            }

        }

        return nil
    }

Для меня этот способ работает почти идеально, потому что я создаю маску только один раз в своем коде (когда создается элемент GUI), поэтому производительность вообще не влияет.

Дело в том, что мне на самом деле нужно, чтобы результирующее изображение (текстура градиента) выглядело так (градиенты должны иметь фиксированную высоту, но размер черной области может варьироваться):

желаемый вид градиентной текстуры

Поскольку фильтр CILinearGradient не имеет параметра inputImage, я не могу соединить два фильтра один за другим. Но скорее мне нужно создать изображение и применить фильтр, а затем создать другое изображение и применить новый фильтр.

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

Интересно, есть ли что-нибудь о CILinearGradient фильтр, о котором я не знаю? Может быть, есть более простой способ решить эту проблему и сделать сложный градиент?

Обратите внимание, что я использую SpriteKit, и решение этой проблемы с помощью CAGradientLayer — не тот путь, которым я хочу идти.


person Whirlwind    schedule 22.12.2015    source источник


Ответы (1)


Я не уверен, что Core Image является подходящим инструментом для этой работы — вам, вероятно, лучше использовать CGGradient функции для рисования вашего изображения.


В сторону: то, о чем вы просите, было бы возможно, но громоздко в CoreImage. Подумайте об этом по аналогии с тем, что вы сделали бы для создания этих изображений как пользователь Photoshop или другого распространенного графического программного обеспечения... способ CoreImage будет примерно таким:

  1. Создайте слой для верхнего градиента, заполните весь холст своим градиентом и трансформируйте слой в нужное положение и размер.
  2. Создайте слой для средней черной части, залейте весь холст черным цветом и трансформируйте слой в нужное положение и размер.
  3. То же самое № 1, но для нижнего градиента.

Вы можете сделать это с помощью комбинации фильтров генератора, фильтров преобразования и фильтров композитинга...

gradient    -> transform -\
                           }-> composite -\
solid color -> transform -/                \
                                            }-> composite
gradient    -> transform ————————————————--/

Но устанавливать это было бы некрасиво.

Точно так же в Photoshop вы можете использовать инструменты выделения и инструменты заливки/градиента, чтобы полностью создать свой дизайн градиент-заливка-градиент в пределах одного слоя... это то, что аналогично рисованию одного изображения с помощью CoreGraphics.


Итак, как сделать однопроходный компьютерный рисунок? Что-то вроде этого...

func createImage(width: CGFloat, _ height: CGFloat) -> CGImage {

    let gradientOffset: CGFloat = 10 // white at bottom and top before/after gradient
    let gradientLength: CGFloat = 12 // height of gradient region

    // create colors and gradient for drawing with
    let space = CGColorSpaceCreateDeviceRGB()
    let startColor = UIColor.whiteColor().CGColor
    let endColor = UIColor.blackColor().CGColor
    let colors: CFArray = [startColor, endColor]
    let gradient = CGGradientCreateWithColors(space, colors, [0,1])

    // start an image context
    UIGraphicsBeginImageContext(CGSize(width: width, height: height))
    defer { UIGraphicsEndImageContext() }
    let context = UIGraphicsGetCurrentContext()

    // fill the whole thing with white (that'll show through at the ends when done)
    CGContextSetFillColorWithColor(context, startColor)
    CGContextFillRect(context, CGRect(x: 0, y: 0, width: width, height: height))

    // draw top gradient
    CGContextDrawLinearGradient(context, gradient, CGPoint(x: 0, y: gradientOffset), CGPoint(x: 0, y: gradientOffset + gradientLength), [])

    // fill solid black middle
    CGContextSetFillColorWithColor(context, endColor)
    CGContextFillRect(context, CGRect(x: 0, y: gradientOffset + gradientLength, width: width, height: height - 2 * gradientOffset - 2 * gradientLength))

    // draw bottom gradient
    CGContextDrawLinearGradient(context, gradient, CGPoint(x: 0, y: height - gradientOffset), CGPoint(x: 0, y: height - gradientOffset - gradientLength), [])

    return CGBitmapContextCreateImage(context)!
}
person rickster    schedule 22.12.2015
comment
Спасибо за отличный пример! Да, это действительно лучшее решение... Не уверен, что вы намеренно использовали белый цвет в своем примере (я пытался сделать градиент от черного к прозрачному), но после изменения (удаления) этой части все стало еще короче, и это просто работает :) - person Whirlwind; 23.12.2015
comment
Вы можете добиться того же с помощью одного вызова CGContextDrawLinearGradient, если вы установите для параметра CGGradientCreateWithColors местоположения что-то вроде [0, 0.1, 0.9, 1.0], а для параметра цвета — [clear, black, black, clear]. - person 0x141E; 23.12.2015
comment
Не совсем: спецификация OP требует, чтобы градиенты имели фиксированную ширину в пикселях, а не пропорцию заполненного пространства. - person rickster; 23.12.2015
comment
@rickster местоположения могут быть рассчитаны в процентах от общей высоты. - person 0x141E; 23.12.2015
comment
Истинный. Наверное, это вопрос стиля. Я сам немного с подозрением отношусь к такого рода пиксельной математике, чтобы что-то важное не оказалось на границе частичного пикселя и не отображалось нечетко. Но это градиент, так что все равно все размыто. - person rickster; 23.12.2015