Фильтр основного изображения CISourceOverCompositing не отображается должным образом с альфа-наложением

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

Я воссоздал проблему в простом проекте Xcode< /а>. Он создает изображение с оранжевым, белым и черным текстом, нарисованным с альфа-каналом 0,3, и это выглядит правильно. Я даже добавил это изображение в Sketch, поместив его поверх фонового изображения, и оно выглядит великолепно. Изображение в нижней части экрана показывает, как это выглядит в Sketch. Проблема в том, что после наложения текста на фон с помощью CISourceOverCompositing белый текст становится слишком непрозрачным, как если бы альфа была равна 0,5, а черный текст едва виден, как если бы альфа была равна 0,1. Верхнее изображение показывает этот образ, созданный программно. Вы можете перетащить ползунок, чтобы настроить альфа-канал (по умолчанию 0,3), который воссоздаст результирующее изображение.

введите здесь описание изображения

Код, конечно же, включен в проект, но также включен и здесь. Это создает наложение текста с альфа-каналом 0,3, которое выглядит, как и ожидалось.

let colorSpace = CGColorSpaceCreateDeviceRGB()
let alphaInfo = CGImageAlphaInfo.premultipliedLast.rawValue

let bitmapContext = CGContext(data: nil, width: Int(imageRect.width), height: Int(imageRect.height), bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace, bitmapInfo: alphaInfo)!
bitmapContext.setAlpha(0.3)
bitmapContext.setTextDrawingMode(CGTextDrawingMode.fill)
bitmapContext.textPosition = CGPoint(x: 20, y: 20)

let displayLineTextWhite = CTLineCreateWithAttributedString(NSAttributedString(string: "hello world", attributes: [.foregroundColor: UIColor.white, .font: UIFont.systemFont(ofSize: 50)]))
CTLineDraw(displayLineTextWhite, bitmapContext)

let textCGImage = bitmapContext.makeImage()!
let textImage = CIImage(cgImage: textCGImage)

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

let combinedFilter = CIFilter(name: "CISourceOverCompositing")!
combinedFilter.setValue(textImage, forKey: "inputImage")
combinedFilter.setValue(backgroundImage, forKey: "inputBackgroundImage")
let outputImage = combinedFilter.outputImage!

person Jordan H    schedule 25.02.2018    source источник
comment
Хотел бы я помочь вам. Этот вопрос действительно привлек мое внимание. В документе Apple предлагается проверить используемую формулу ( keithp.com/~keithp/porterduff /p253-porter.pdf ). вы проверили? В частности, страница 4, разделы 4.3? Это немного по-гречески для моего текущего использования CI, но, может быть, это поможет вам? Похоже, что умножение альфы может происходить неожиданно?   -  person dfd    schedule 28.02.2018
comment
Спасибо @dfd, это хорошая мысль, но я не понимаю, как это сделать. Я обновил вопрос с более подробной информацией и примером проекта, если вам что-то выделяется!   -  person Jordan H    schedule 04.03.2018
comment
@ Джоуи, ты не поверишь, но у меня такая же проблема, и я много пробовал, но не нашел подходящих решений, поэтому я попробовал один трюк, я только что поместил один белый UIView за изображение, чтобы оно работало отлично. так что попробуй может поможет :)   -  person Ravi Panchal    schedule 05.03.2018


Ответы (3)


После долгих проб и ошибок (спасибо @andy и @Juraj Antas за то, что подтолкнули меня в правильном направлении) у меня наконец есть ответ. Таким образом, рисование в контексте базовой графики приводит к правильному внешнему виду, но рисование изображений с использованием этого подхода обходится дороже. Казалось, проблема была с CISourceOverCompositing, но на самом деле проблема заключается в том, что по умолчанию фильтры Core Image работают в линейном пространстве, тогда как Core Graphics работает в перцептивном пространстве, что объясняет разные результаты. Однако вы можете создать изображение базовой графики из фильтра основного изображения, используя контекст основного изображения, который не выполняет управление цветом, что соответствует выходным данным подхода базовой графики. Так что исходный код был в порядке, просто нужно было вывести изображение немного по-другому.

let ciContext = CIContext(options: [kCIContextWorkingColorSpace: NSNull()])
let outputImage = ciContext.createCGImage(outputCIImage, from: outputCIImage.extent) 
//this image appears as expected
person Jordan H    schedule 24.03.2018
comment
Я также добавил outputColorSpace. Для быстрого 4: let ciContext = CIContext(options: [CIContextOption.outputColorSpace: NSNull(), CIContextOption.workingColorSpace: NSNull()]) - person lenooh; 29.10.2018

ДЛЯ ЧЕРНО-БЕЛОГО ТЕКСТА

Если вы используете операцию композитинга .normal, вы определенно получите не такой результат, как при использовании .hardLight. На вашей картинке показан результат .hardLight операции.

.normal операция является классической OVER операцией с формулой: (Изображение1 * A1) + (Изображение2 * (1 – A1)).

Вот предварительно умноженный текст (RGB * A), поэтому шаблон RGB зависит от непрозрачности A в этом конкретном случае. RGB текстового изображения может содержать любой цвет, в том числе и черный. Если A=0 (черный альфа-канал) и RGB=0 (черный цвет) и ваше изображение предварительно умножено – все изображение полностью прозрачно, если A=1 (белый альфа-канал) и RGB=0 (черный цвет) – изображение непрозрачно чернить.

Если ваш текст не имеет альфа-канала при использовании операции .normal, я получу операцию ADD: Изображение1 + Изображение2.


Чтобы получить то, что вы хотите, вам нужно настроить операцию композитинга на .hardLight.

.hardLight операция композитинга работает как .multiply

если альфа текстового изображения менее 50 процентов (A ‹ 0,5, изображение почти прозрачное)

Формула для .multiply: Изображение1 * Изображение2


.hardLight операция композитинга работает как .screen

если альфа текстового изображения больше или равна 50 процентам (A >= 0,5, изображение полупрозрачное)

Формула 1 для .screen: (Изображение1 + Изображение2) – (Изображение1 * Изображение2)

Формула 2 для .screen: 1 – (1 – Изображение1) * (1 – Изображение2)

Операция .screen имеет гораздо более мягкий результат, чем .plus, и позволяет сохранить альфу не больше 1 (операция plus добавляет альфы Image1 и Image2, так что вы можете получить alpha = 2, если у вас есть две альфы). .screen операция композитинга хороша для создания отражений.

введите здесь описание изображения

func editImage() {

    print("Drawing image with \(selectedOpacity) alpha")

    let text = "hello world"
    let backgroundCGImage = #imageLiteral(resourceName: "background").cgImage!
    let backgroundImage = CIImage(cgImage: backgroundCGImage)
    let imageRect = backgroundImage.extent

    //set up transparent context and draw text on top
    let colorSpace = CGColorSpaceCreateDeviceRGB()
    let alphaInfo = CGImageAlphaInfo.premultipliedLast.rawValue

    let bitmapContext = CGContext(data: nil, width: Int(imageRect.width), height: Int(imageRect.height), bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace, bitmapInfo: alphaInfo)!
    bitmapContext.draw(backgroundCGImage, in: imageRect)

    bitmapContext.setAlpha(CGFloat(selectedOpacity))
    bitmapContext.setTextDrawingMode(.fill)

    //TRY THREE COMPOSITING OPERATIONS HERE 
    bitmapContext.setBlendMode(.hardLight)
    //bitmapContext.setBlendMode(.multiply)
    //bitmapContext.setBlendMode(.screen)

    //white text
    bitmapContext.textPosition = CGPoint(x: 15 * UIScreen.main.scale, y: (20 + 60) * UIScreen.main.scale)
    let displayLineTextWhite = CTLineCreateWithAttributedString(NSAttributedString(string: text, attributes: [.foregroundColor: UIColor.white, .font: UIFont.systemFont(ofSize: 58 * UIScreen.main.scale)]))
    CTLineDraw(displayLineTextWhite, bitmapContext)

    //black text
    bitmapContext.textPosition = CGPoint(x: 15 * UIScreen.main.scale, y: 20 * UIScreen.main.scale)
    let displayLineTextBlack = CTLineCreateWithAttributedString(NSAttributedString(string: text, attributes: [.foregroundColor: UIColor.black, .font: UIFont.systemFont(ofSize: 58 * UIScreen.main.scale)]))
    CTLineDraw(displayLineTextBlack, bitmapContext)

    let outputImage = bitmapContext.makeImage()!

    topImageView.image = UIImage(cgImage: outputImage)
}

Итак, для воссоздания этой операции композитинга вам нужна следующая логика:

//rgb1 – text image 
//rgb2 - background
//a1   - alpha of text image

if a1 >= 0.5 { 
    //use this formula for compositing: 1–(1–rgb1)*(1–rgb2) 
} else { 
    //use this formula for compositing: rgb1*rgb2 
}

Я воссоздал изображение с помощью приложения для композитинга The Foundry NUKE 11. Смещение = 0,5, здесь Add = 0,5.

Я использовал свойство Offset=0.5, потому что transparency=0.5 является операцией композитинга pivot point из .hardLight.

введите здесь описание изображения

введите здесь описание изображения

ДЛЯ ЦВЕТНОГО ТЕКСТА

Вам нужно использовать операцию композитинга .sourceAtop, если у вас есть текст ОРАНЖЕВОГО (или любого другого цвета) в дополнение к черно-белому тексту. Применяя .sourceAtop случай метода .setBlendMode, вы заставляете Swift использовать яркость фонового изображения, чтобы определить, что показывать. В качестве альтернативы вы можете использовать базовый фильтр изображения CISourceAtopCompositing вместо CISourceOverCompositing.

bitmapContext.setBlendMode(.sourceAtop)

or

let compositingFilter = CIFilter(name: "CISourceAtopCompositing")

.sourceAtop операция имеет следующую формулу: (Изображение1 * A2) + (Изображение2 * (1 – A1)). Как видите, вам нужны два альфа-канала: A1 — это альфа-канал для текста, а A2 — альфа-канал для фонового изображения.

bitmapContext.textPosition = CGPoint(x: 15 * UIScreen.main.scale, y: (20 + 60) * UIScreen.main.scale)
let displayLineTextOrange = CTLineCreateWithAttributedString(NSAttributedString(string: text, attributes: [.foregroundColor: UIColor.orange, .font: UIFont.systemFont(ofSize: 58 * UIScreen.main.scale)]))
CTLineDraw(displayLineTextOrange, bitmapContext)

введите здесь описание изображения

введите здесь описание изображения

person Andy Fedoroff    schedule 06.03.2018
comment
почти правильно, но это зависит от цвета изображения, а не альфа, какой режим наложения используется. - person Juraj Antas; 06.03.2018
comment
в данном случае это зависит от обоих: RGB и A. Математика применяется к RGB, альфа-канал управляет непрозрачностью. Потому что здесь паттерн RGB*A (предварительно умноженное изображение). - person Andy Fedoroff; 06.03.2018
comment
Вы знаете, как A вычисляется в вашей математике? - person Juraj Antas; 06.03.2018
comment
не могли бы вы написать это здесь? Я тестирую в пользовательском CIFilter. - person Juraj Antas; 06.03.2018
comment
Что именно написать? - person Andy Fedoroff; 06.03.2018
comment
как получить float из vec4 или, другими словами, из входного rgba вам нужно получить одно число, которое вы сравниваете с 0,5, и выбираете формулу смешивания. - person Juraj Antas; 06.03.2018
comment
Например: у вас есть предварительно умноженное изображение rgba. Когда вы используете ползунок, вы должны использовать только альфу, потому что понижение A снижает RGB. Используйте переменную для альфы в vec4. - person Andy Fedoroff; 06.03.2018
comment
сгенерированное изображение не использует альфа-канал с предварительным умножением. Это будет только rgb. но это ргба. Сейчас я пытаюсь точно воссоздать .hardLight в пользовательском CIFilter. - person Juraj Antas; 06.03.2018
comment
comment
Это определенно контролируется альфой. - person Andy Fedoroff; 06.03.2018
comment
все изображение имеет альфа == 0,3 - person Juraj Antas; 06.03.2018
comment
@IBAction func sliderValueChanged(_ sender: UISlider) { selectedOpacity = sender.value editImage() } - person Andy Fedoroff; 06.03.2018
comment
Да, альфа == 0,3, ползунок управляет альфой, а альфа управляет RGB. - person Andy Fedoroff; 06.03.2018
comment
Итак, A*RGB, и если R=1, B=1, G=1, RGB == 0,3 - person Andy Fedoroff; 06.03.2018
comment
Это то, что я сказал. - person Andy Fedoroff; 06.03.2018
comment
а для R=0, G=0, B=0, A=0,3 -> A*RGB == 0,0 ? Это означало бы, что в обоих случаях оно меньше 0,5, и всегда используется умножение... неправда. - person Juraj Antas; 06.03.2018
comment
Логика неверна. A управляет RGB, а не RGB управляет A. - person Andy Fedoroff; 06.03.2018
comment
А может быть 0,5 или 0,1. Итак, А*RGB. И RGB=0,5 или RGB=0,1 - person Andy Fedoroff; 06.03.2018
comment
RGB-изображение может содержать любой цвет, в том числе и черный. Если A=0 и RGB=0 и изображение предварительно умножено – все изображение прозрачно, если A=1 и RGB=0 изображение непрозрачно-черное. - person Andy Fedoroff; 06.03.2018
comment
это то, что я знаю. Но это не помогает мне воссоздать правильный режим наложения. - person Juraj Antas; 06.03.2018
comment
какую операцию композитинга вы воссоздаете? .жесткий свет? - person Andy Fedoroff; 06.03.2018
comment
if a1 ›= 0.5 { // 1 – (1 – rgb1) * (1 – rgb2) } else { // rgb1 * rgb2 } - person Andy Fedoroff; 06.03.2018
comment
проблема в A, если это альфа, то это константа 0,3, если это RGB * A, то снова это 0,3 или 0, в зависимости от того, белый или черный текст ... так что нет случая, когда это больше 0,5;) поэтому я думаю, что он вычисляется по цвету, а не по альфа-каналу. - person Juraj Antas; 06.03.2018
comment
Вы были правы — альфа никак не влияет на работу hardLight. Только на прозрачность. - person Andy Fedoroff; 06.03.2018
comment
Привет, Энди, спасибо за ответ. Жесткий свет выглядит правильно для чисто черно-белого текста, но, к сожалению, для цветного текста, такого как, например, UIColor.orange, он выглядит не так, как ожидалось, поскольку он ненадлежащим образом смешивается с фоном, поэтому он может казаться красным, а не равномерно оранжевым. Я действительно хочу добиться нормального режима наложения, а не жесткого света, умножения или экрана. - person Jordan H; 06.03.2018
comment
@Joey, если он воссоздан с предварительной цветокоррекцией (что вы можете видеть в интерфейсе NUKE), он выглядит нормально. - person Andy Fedoroff; 06.03.2018
comment
@ Джоуи, если вы хотите получить режим наложения .normal (это классическая операция компоновки OVER), формула будет следующей: RGB1 * A1 + RGB2 * (1–A1) - person Andy Fedoroff; 06.03.2018
comment
проблема в цветовом пространстве. Общий RGB на iOS, sRGB в графических программах. Без преобразования между пробелами результат никогда не будет таким же. - person Juraj Antas; 06.03.2018
comment
Свойство гаммы может компенсировать кривую sRGB. - person Andy Fedoroff; 06.03.2018
comment
Для оранжевого/черного/белого цветов используйте операцию .sourceAtop. - person Andy Fedoroff; 06.03.2018
comment
Да, sourceAtop это все, что нужно! Интересно, что я также пробовал CISourceAtopCompositing, но, насколько я могу судить, он выглядит точно так же, как CISourceOverCompositing . Странный. В любом случае, если вы обновите ответ, я приму его! Спасибо! - person Jordan H; 07.03.2018
comment
Я попробовал CISourceAtopCompositing CIFilter, но это не привело к такому же результату, как при использовании режима наложения sourceAtop. Он выглядит так же, как CISourceOverCompositing. Но ваш ответ говорит, что он получит те же результаты. Я бы тоже подумал, но это не так, как ни странно. - person Jordan H; 07.03.2018
comment
Сначала вам нужно добавить альфа-канал к фоновому изображению. - person Andy Fedoroff; 07.03.2018
comment
Эй, @andy, не могли бы вы уточнить, что вы имеете в виду, добавив сначала альфа-канал к фоновому изображению, чтобы это работало с использованием CISourceAtopCompositing? Я сделал часть фонового изображения прозрачным, но продолжаю получать тот же результат. - person Jordan H; 18.03.2018
comment
Формула для операции Atop: Ab+B(1-a). Полученное изображение показывает форму изображения B, где A покрывает B, где изображения перекрываются. - person Andy Fedoroff; 18.03.2018
comment
a — альфа-канал изображения переднего плана, b — альфа-канал фонового изображения. - person Andy Fedoroff; 18.03.2018
comment
A — это каналы RGB основного изображения, B — каналы RGB фонового изображения. - person Andy Fedoroff; 18.03.2018
comment
Как говорит формула, вам обязательно нужна альфа для фона. - person Andy Fedoroff; 18.03.2018
comment
Хорошо, хм, с альфой на фоновом изображении я получаю тот же неожиданный результат. Можете ли вы попробовать это с примером проекта и посмотреть, сможете ли вы заставить его работать с CISourceAtopCompositing? - person Jordan H; 18.03.2018
comment
На данный момент я не могу связаться с Xcode. Только завтра))) - person Andy Fedoroff; 18.03.2018
comment
Вы определенно правы в том, что проблема вызвана фоновым изображением. Если я создаю новое изображение, рисуя фон в контексте и получая его через makeImage(), чтобы использовать его вместо этого, оно выглядит так, как ожидалось. Но это действительно неэффективно и использует много оперативной памяти для больших фотографий. Мне интересно, какой эффективный способ получить это фоновое изображение в правильном формате. - person Jordan H; 18.03.2018
comment
Я попробовал это сегодня. Кажется, все в порядке с композитингом op. - person Andy Fedoroff; 18.03.2018
comment
Я полагаю, что единственный эффективный способ получить фоновое изображение в правильном формате — это подготовить его в приложении для композитинга, таком как Nuke или Fusion. - person Andy Fedoroff; 18.03.2018
comment
Давайте продолжим обсуждение в чате. - person Andy Fedoroff; 18.03.2018

Окончательный ответ: Формула в CISourceOverCompositing хороша. Это правильно.

НО

Он работает в неправильном цветовом пространстве. В графических программах у вас скорее всего цветовое пространство sRGB. В iOS используется цветовое пространство Generic RGB. Вот почему результаты не совпадают.

Используя пользовательский фильтр CIFilter, я воссоздал фильтр CISourceOverCompositing.
s1 — текстовое изображение.
s2 — фоновое изображение.

Ядро для него такое:

 kernel vec4 opacity( __sample s1, __sample s2) {
     vec3 text = s1.rgb;
     float textAlpha = s1.a;
     vec3 background = s2.rgb;

     vec3 res = background * (1.0 - textAlpha) + text;
     return vec4(res, 1.0);
 }

Поэтому, чтобы исправить эту «проблему» с цветом, вы должны преобразовать текстовое изображение из RGB в sRGB. Я думаю, ваш следующий вопрос будет о том, как это сделать ;)

Важно: iOS не поддерживает аппаратно-независимые или общие цветовые пространства. Приложения iOS должны вместо этого использовать цветовые пространства устройства. документ Apple о цветовых пространствах

тестовое изображение с цветовыми пространствами RGB и sRGB

person Juraj Antas    schedule 05.03.2018
comment
Спасибо за Ваш ответ! К сожалению, несмотря на то, что это выглядит, как и ожидалось, с использованием моего теста 50% серого, это выглядит неправильно на реальных фотографиях из-за режима наложения наложения. Я обновил проект, чтобы использовать реальную фотографию, которая более четко демонстрирует проблему, и добавил предложенные вами изменения, которые вы можете раскомментировать, чтобы попробовать их с реальным фоновым изображением. - person Jordan H; 06.03.2018
comment
Попробуйте заменить .overlay на .hardLight. Результат, что вы после? Если не только другим способом, это настраиваемый CIFilter. - person Juraj Antas; 06.03.2018
comment
Хм, жесткий свет хорошо работает для черно-белого текста, но оранжевый текст кажется красным. - person Jordan H; 06.03.2018
comment
Я тестирую собственный CIFilter, мне нужно найти хорошую математическую формулу для этого нормального режима наложения. используя формулы оттуда: simplefilter.de/en/basics/mixmods.html - person Juraj Antas; 06.03.2018
comment
С пользовательским CIFilter я получил тот же результат, что и с CISourceOverCompositing. Формула CISourceOverCompositing: vec3 res = background * (1.0 - textAlpha) + text; vec3 — вектор цвета RGB. Теперь все еще остается вопрос, какая формула композиции является правильной, которую графические программы используют для обычного режима наложения. С трудом найдёшь. - person Juraj Antas; 06.03.2018
comment
Я начинаю думать, что проблема не в формуле, а в цветовом пространстве. Графические программы используют sRGB.... - person Juraj Antas; 06.03.2018
comment
Хм, я пытался заменить CGColorSpaceCreateDeviceRGB() на CGColorSpace(name: CGColorSpace.sRGB), но результат тот же. - person Jordan H; 06.03.2018
comment
Да, знаю. есть подвох. Важно: iOS не поддерживает аппаратно-независимые или общие цветовые пространства. Приложения iOS должны вместо этого использовать цветовые пространства устройства. developer.apple.com/library/content/ документация/ - person Juraj Antas; 06.03.2018
comment
Другими словами, Apple говорит, что вам нужно сделать это самостоятельно. Это не так сложно (но и не совсем просто, цветовые пространства сложны) ryanjuckett.com/programming/rgb-color-space-conversion/?start=2 - person Juraj Antas; 06.03.2018
comment
Спасибо за Ваш ответ! Я смог получить желаемые результаты, просто используя режим наложения sourceAtop вместо hardLight, если вы не видели этого в других комментариях. - person Jordan H; 07.03.2018
comment
да, понял. простое решение всегда лучшее. Ответ Энди хорош, за исключением того, что A в его уравнениях не является альфой. Альфа в вашем тестовом изображении никогда не превышает 0,3. Это легкость, и ее можно рассчитать следующим образом: плавающая легкость = (0,2126*b.r + 0,7152*b.g + 0,0722*b.b). Он должен исправить это. HardLight хорош только для серых или почти серых изображений, так как при использовании цветов вы получаете сдвиг цвета. - person Juraj Antas; 08.03.2018