关于实现Image的动态图片的播放

本文介绍如何在Swift中实现动态GIF图的播放功能。通过继承UIImage并重写其方法,可以轻松地将GIF图加载到UIImageView中进行播放。文章提供了完整的代码示例,并解释了关键步骤。

最近在写Swift的时候,需要有一个动态播放头像的功能,于是就自己试着写了个。

这个需要继承UIImage,重写它的方法,全部代码直接贴上去

//

//  UIImageGIF.swift

//  SwiftYiRen

//

//  Created by 李文强 on 2017/11/1.

//  Copyright © 2017年 李文强. All rights reserved.

//

 

import UIKit

import ImageIO

extension UIImageView{

    public func loadGif(name: String) {

        DispatchQueue.global().async {

            let image = UIImage.gif(name: name)

            DispatchQueue.main.async {

                self.image = image

            }

        }

    }

 

}

 

extension UIImage {

    

    public class func gif(data: Data) -> UIImage? {

        // Create source from data

        guard let source = CGImageSourceCreateWithData(data as CFData, nil) else {

            print("SwiftGif: Source for the image does not exist")

            return nil

        }

        

        return UIImage.animatedImageWithSource(source)

    }

    

    public class func gif(url: String) -> UIImage? {

        guard let bundleURL = URL(string: url) else {

            print("SwiftGif: This image named \"\(url)\" does not exist")

            return nil

        }

        guard let imageData = try? Data(contentsOf: bundleURL) else {

            print("SwiftGif: Cannot turn image named \"\(url)\" into NSData")

            return nil

        }

        

        return gif(data: imageData)

    }

    

    public class func gif(name: String) -> UIImage? {

        guard let bundleURL = Bundle.main

            .url(forResource: name, withExtension: "gif") else {

                print("SwiftGif: This image named \"\(name)\" does not exist")

                return nil

        }

        

        // Validate data

        guard let imageData = try? Data(contentsOf: bundleURL) else {

            print("SwiftGif: Cannot turn image named \"\(name)\" into NSData")

            return nil

        }

        

        return gif(data: imageData)

    }

    

    internal class func delayForImageAtIndex(_ index: Int, source: CGImageSource!) -> Double {

        var delay = 0.1

        let cfProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil)

        let gifPropertiesPointer = UnsafeMutablePointer<UnsafeRawPointer?>.allocate(capacity: 0)

        if CFDictionaryGetValueIfPresent(cfProperties, Unmanaged.passUnretained(kCGImagePropertyGIFDictionary).toOpaque(), gifPropertiesPointer) == false {

            return delay

        }

        

        let gifProperties:CFDictionary = unsafeBitCast(gifPropertiesPointer.pointee, to: CFDictionary.self)

        

        // Get delay time

        var delayObject: AnyObject = unsafeBitCast(

            CFDictionaryGetValue(gifProperties,

                                 Unmanaged.passUnretained(kCGImagePropertyGIFUnclampedDelayTime).toOpaque()),

            to: AnyObject.self)

        if delayObject.doubleValue == 0 {

            delayObject = unsafeBitCast(CFDictionaryGetValue(gifProperties,

                                                             Unmanaged.passUnretained(kCGImagePropertyGIFDelayTime).toOpaque()), to: AnyObject.self)

        }

        

        delay = delayObject as? Double ?? 0

        

        if delay < 0.1 {

            delay = 0.1 // Make sure they're not too fast

        }

        

        return delay

    }

    

    internal class func gcdForPair(_ a: Int?, _ b: Int?) -> Int {

        var a = a

        var b = b

        // Check if one of them is nil

        if b == nil || a == nil {

            if b != nil {

                return b!

            } else if a != nil {

                return a!

            } else {

                return 0

            }

        }

        

        // Swap for modulo

        if a! < b! {

            let c = a

            a = b

            b = c

        }

        

        // Get greatest common divisor

        var rest: Int

        while true {

            rest = a! % b!

            

            if rest == 0 {

                return b! // Found it

            } else {

                a = b

                b = rest

            }

        }

    }

    

    internal class func gcdForArray(_ array: Array<Int>) -> Int {

        if array.isEmpty {

            return 1

        }

        

        var gcd = array[0]

        

        for val in array {

            gcd = UIImage.gcdForPair(val, gcd)

        }

        

        return gcd

    }

    

    internal class func animatedImageWithSource(_ source: CGImageSource) -> UIImage? {

        let count = CGImageSourceGetCount(source)

        var images = [CGImage]()

        var delays = [Int]()

        for i in 0..<count {

            // 添加图片

            if let image = CGImageSourceCreateImageAtIndex(source, i, nil) {

                images.append(image)

            }

                        let delaySeconds = UIImage.delayForImageAtIndex(Int(i),

                                                            source: source)

            delays.append(Int(delaySeconds * 1000.0)) // Seconds to ms

        }

        

        // Calculate full duration

        let duration: Int = {

            var sum = 0

            

            for val: Int in delays {

                sum += val

            }

            

            return sum

        }()

        

        // Get frames

        let gcd = gcdForArray(delays)

        var frames = [UIImage]()

        

        var frame: UIImage

        var frameCount: Int

        for i in 0..<count {

            frame = UIImage(cgImage: images[Int(i)])

            frameCount = Int(delays[Int(i)] / gcd)

            

            for _ in 0..<frameCount {

                frames.append(frame)

            }

        }

        let animation = UIImage.animatedImage(with: frames,

                                              duration: Double(duration) / 1000.0)

        

        return animation

    }

    

}

使用

    //头像

    var myHeaderImage:UIImageView = {

        let image = UIImageView.init(frame: CGRect(x:0,y:0,width:80,height:80))

        image.backgroundColor = UIColor.blue

        image.layer.cornerRadius = 40

        image.image = UIImage.gif(name:"GIF图片名(自己工程里的)")

        image.layer.masksToBounds = true

        return image

    }()

转载于:https://my.oschina.net/u/2986115/blog/1581068

使用GDI+库显示gif动态图片,该类接口如下: 可以看出,该ImageEx完全继承了基类的接口函数。 说明: 如果打开非多帧图片,该类几乎完全等价于基类,比如你可以把该类的对象代入Graphics类系列的成员函数中; 如果打开的是多帧的图片,你只要打开图片后不调用InitAnimation函数(它会创建线程),则上述做法依然可以; 但如果调用InitAnimation函数后(单帧图像没关系,因为不会创建线程),则不可以了, 所有的基类继承过来的接口成员函数和配合gdi+库其他类的函数调用都是不可以的,因为没有作线程同步, 你只能调用下面位数不多的几个public成员函数,调用Destroy成员函数后,则就可以了,因为它会关闭线程。 其实你会发现下面的public成员函数操作的成员变量都是新增的成员变量,没涉及到线程同步问题。 class ImageEx : public Image { public: //以长度为nSize的内存pBuff中的内容构造图像 ImageEx(const void* pBuff, size_t nSize, BOOL useEmbeddedColorManagement = FALSE); //以类型为sResourceType,名称为sResource的资源构造图像 ImageEx(LPCTSTR sResourceType, LPCTSTR sResource, BOOL useEmbeddedColorManagement = FALSE); //以文件构造图像 ImageEx(LPCTSTR filename, BOOL useEmbeddedColorManagement = FALSE); //调用Destroy成员函数 ~ImageEx(); public: //如果已经构造的对象是动画,则创建动画线程,并返回true, //如果为静态图像或已经创建过动画线程,则也返回false // 图像将绘制在m_hWnd客户区的rect区域,会拉伸,支持镜像 bool InitAnimation(HWND hWnd, RECT rect); //判断是否为动画 bool IsAnimatedGIF() { return m_nFrameCount > 1; } //设置动画暂停与否 void SetPause(bool bPause); //判断动画是否处于暂停状态 bool IsPaused() { return m_bPause; } //关闭动画,事实上基类Image中还有的两个成员变量没有关闭,因为析构函数会调用基类析构函数进行关闭的 void Destroy(); //另外的非public的东西省略.. }; 用法: MFC对话框程序在下面添加: BOOL CTestDlgDlg::OnInitDialog() { CDialog::OnInitDialog(); //其它的初始化代码 // GDI+ //m_imageImageEx指针类型成员变量,"GIF"为资源类型,"HEARTS"为资源名称 m_image = new ImageEx( _T("GIF"), _T("HEARTS") ); RECT rc; GetClientRect(&rc); m_image->InitAnimation(this->m_hWnd, rc);//创建gif播放线程 return TRUE; // return TRUE unless you set the focus to a control } CTestDlgDlg::~CTestDlgDlg() { // GDI+ delete m_image; } 其中的m_image = new ImageEx( _T("GIF"), _T("HEARTS") );你可以换成ImageEx类的另外两个构造函数
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值