web-dev-qa-db-fra.com

Comment animer un effet de flou gaussien sous iOS?

Pour l’ensemble de la sensation iOS 7, je souhaite appliquer un effet de flou à une partie spécifique de l’écran afin de l’obscurcir, mais je ne souhaite pas simplement jeter le flou immédiatement, je souhaite l’animer et l’animer afin que l'utilisateur voit presque l'effet de flou appliqué.

Presque comme si dans Photoshop, la valeur du flou gaussien avait été modifiée progressivement, de 0 à 10, au lieu de 0 à 10 en une fois.

J'ai essayé plusieurs solutions, la suggestion la plus courante étant de simplement placer la vue floue au-dessus d'une vue non floue, puis d'abaisser la valeur alpha de la vue floue.

Cela fonctionne ok, mais pas très agréable à regarder car il n'y a pas de transition, c'est juste une superposition. Exemple:

enter image description here

Quel serait un meilleur moyen d'obtenir un tel effet? Je suis familier avec GPUImage , mais je ne sais pas comment y parvenir.

Il serait également intéressant de pouvoir contrôler le pourcentage de flou auquel le flou est appliqué, afin de pouvoir l'appliquer de manière interactive à partir de l'utilisateur. (exemple: l'utilisateur traîne à moitié, le flou est à moitié appliqué, etc.)

24
user2005643

Avec iOS 9 et les versions ultérieures, vous pouvez animer l'effet sur les vues des effets visuels:

[UIView animateWithDuration:0.3 animations: ^ {
    visualEffectView.effect = blurEffect;
} completion:nil];

Vous obtiendrez ainsi l’effet attendu d’animer le rayon de flou.


Je pense que si vous animez un fondu enchaîné à partir d'une vue non floue -> vue floue, ce sera assez agréable. Vous devez animer l'affichage de la vue floue au-dessus de la vue non floue.

Supposons que blurView est la vue contenant l’effet de flou.

Voici deux exemples de réalisation d'une transition animée:

[UIView transitionWithView:self.view duration:0.3 options:UIViewAnimationOptionTransitionCrossDissolve animations: ^ {
    [self.view addSubview:blurView];
} completion:nil];

Ici, je suppose que blurView a déjà été configuré et qu'il suffit de faire la transition en tant que sous-vue dans une animation.

Vous pouvez également y parvenir d'une manière différente:

blurView.alpha = 0.0f;
[UIView animateWithDuration:0.3 animations: ^ {
    blurView.alpha = 1.0f;
} completion:nil];

Ici, je rend la vue floue transparente et j'anime son apparence. Notez que l'utilisation de cette méthode ne fonctionnera pas avec hidden. Vous souhaitez donc utiliser la propriété alpha.


Si vous souhaitez contrôler le flou de manière interactive, voici un exemple sur la façon d’atteindre:

Commencez par créer une image de la vue que vous souhaitez rendre floue:

UIGraphicsBeginImageContextWithOptions(nonBlurredView.bounds.size, NO, self.view.window.screen.scale);
[nonBlurredView drawViewHierarchyInRect:nonBlurredView.bounds afterScreenUpdates:NO];
UIImage *snapshotImage = UIGraphicsGetImageFromCurrentImageContext();

Gardez cette image. Vous voulez redessiner de la même manière si la vue a été modifiée. Sinon, vous devriez réutiliser cette image, car il est coûteux de tracer la hiérarchie.

Maintenant, lorsque vous avez besoin de flou, utilisez le code suivant:

CIImage *imageToBlur = [CIImage imageWithCGImage:snapshotImage.CGImage];    
CIFilter *gaussianBlurFilter = [CIFilter filterWithName: @"CIGaussianBlur"]; 
[gaussianBlurFilter setValue:imageToBlur forKey: @"inputImage"]; 
[gaussianBlurFilter setValue:@10 forKey: @"inputRadius"]; //Here you input the blur radius - the larger, the 
CIImage *resultImage = [gaussianBlurFilter valueForKey: @"outputImage"]; 
UIImage *endImage = [[UIImage alloc] initWithCIImage:resultImage];

L'entrée inputRadius donne la quantité de flou réalisée. Si vous animez cela, vous obtiendrez une sensation interactive. 

Mais je pense toujours que l'ancienne méthode est beaucoup plus simple et que l'utilisateur se sentira aussi bien.

44
Leo Natan

Ceci est facile sous OS X, où une vue peut donner un effet à la vue derrière CIFilter et où le processeur est puissant et rapide. Sur iOS, cependant, il n'y a pas de filtres de composition de vues et le flou prend du temps: vous ne pouvez pas le faire de manière répétée, en direct, aussi rapidement que les images de l'animation. Par conséquent, une forme de compromis est nécessaire.

Si vous savez suffisamment à l'avance ce que vous voulez rendre flou, vous pouvez préparer une série d'images en utilisant une gradation de flou: pas de flou, un peu de flou, un peu plus de flou, etc. Ensuite, vous assemblez les images et les "jouez" en tant que "cadres" d'un UIImageView animé.

Ce n'est cependant pas ce que je conseillerais réellement. Ce que je voudrais faire, c'est préparer l'image floue et l'image non floue et utiliser une transition CIDissolveTransition pour dissoudre l'image non floue en une image floue. Non, ce n'est pas la même chose que d'appliquer progressivement le flou, mais c'est peu coûteux en calcul et c'est le meilleur effet de "dissolution" de la boîte à outils.

6
matt

J'ai développé un petit projet qui utilise GPUImage pour le flou réel avec un rayon de flou et un nombre d'images par seconde variables ( MSLiveBlur ). Cela ressemble exactement à ce dont vous avez besoin.

Dans l'exemple d'application, j'ai un curseur qui augmente/diminue le niveau de flou en réponse à l'action de l'utilisateur, comme vous l'avez mentionné dans votre question. Si vous souhaitez l'animer sans interaction de l'utilisateur, vous pouvez créer une minuterie qui augmente progressivement le rayon de flou jusqu'à atteindre la valeur finale. Cependant, vous ne pourrez pas utiliser CoreAnimation avec cette solution.

[[MSLiveBlurView sharedInstance] setBlurInterval:0.2];
[[MSLiveBlurView sharedInstance] blurRect:someView.frame];

// Count x from 0 to your final radius
[MSLiveBlurView sharedInstance].blurRadius = x;
3
michaels

Je suggère rarement des solutions prêtes à l'emploi pour résoudre les problèmes de codage. Cependant, dans ce cas, je recommanderais vivement LiveFrost . Il s'agit d'une sous-classe UIView vraiment solide qui se concentre exclusivement sur la reproduction du populaire effet de flou gaussien iOS 7. 

Je vous encourage également à lire le blog de l'auteur concernant ses décisions en matière de conception pour ce cours. J'ai fait beaucoup de recherches spécifiquement sur ce sujet depuis la sortie d'iOS 7, et je n'ai littéralement aucune plainte à propos de ce code.

Il a même une animation floue hors de la boîte! Bonne chance à toi :)

3
Sam

Voici une fonction que vous pouvez utiliser pour animer un flou avec des images précalculées. Vous dites que vous connaissez GPUImage, je l’ai donc utilisé (mais cela peut fonctionner aussi avec un simple algorithme de flou). 

Avec cela, vous pouvez animer flou réel en temps réel avec un coût de 1 à 2% de la CPU

// configuration
let imageCount: Int = 10
let blurMax: Float = 40.0
// keep images in memory
var currentIdx: Int = 0
var imgs: [UIImage] = []

// func that create the precomputed images with GPUImage (call it in the viewDidLoad for example)
func initBlur() {
    let blur = GaussianBlur()
    let myImage: UIImage = UIImage(named: "your_image")!
    let pictureInput = PictureInput(image: myImage)
    let pictureOutput = PictureOutput()
    pictureOutput.onlyCaptureNextFrame = false

    pictureOutput.imageAvailableCallback = { image in
        self.imgs.append(image)
    }

    pictureInput --> blur --> pictureOutput

    for i in 0...imageCount {
        blur.blurRadiusInPixels = (Float(i) / Float(imageCount)) * blurMax
        pictureInput.processImage(synchronously: true)
    }
}

// function that return the correct image from the image set with a blur percentage from a value between 0 and 1 where 0 = no blur and 1 full blurred.
func getBlurredImage(value: Float) -> UIImage? {
    // return nil if there isn't precompiled images
    if imgs.count == 0 {
        return nil
    }

    // get the desired blurred image index
    let idx = Int(value * Float(imageCount - 1))
    // if the index changed, check the correctness of the index
    if currentIdx != idx {
        if idx < 0 {
            currentIdx = 0
        }
        else if idx >= imgs.count {
            currentIdx = imageCount - 1
        }
        else {
            currentIdx = idx
        }
    }
    return imgs[currentIdx]
}

Et voilà, vous pouvez ensuite l’utiliser par exemple avec une minuterie ou dans une fonctionscrollViewDidScrollcomme:

imageView.image = getBlurredImage(value: yOffset / height)
1
Anthony