简介

图片是一种运用十分广泛的文件格局,APP中也存在很多的图片图标

方案

图片基础知识

iOS - 理解图片

图片的描绘

图片的描绘首要分为位图(bitmap)和矢量图(vector),图片扩大之后咱们能够明晰的看到它们的本质和差异

iOS - 理解图片

位图bitmap

bitmap便是a map of bit,由图片上一组像素点组成,每个点又是经过RGB三原色组成。假如是通明图片的话,咱们还需要信息来描绘通明的程度。这是位图描绘

矢量图vector

把图片看成是几何图画的组成的话,那么能够描绘成图形制作,比方制作一条线,在画布坐标上从哪里制作到哪里,用了什么色彩。经过把这些几何描绘组成起来构成一个图片。这个是矢量描绘

图片紧缩

为什么要紧缩图片呢?因为数据越小,网络传输越快。

有损紧缩

在位图描绘中,咱们经过对每个点的RGB和通明度的描绘来确定图片的信息,RGB每一个色彩域通常运用256色来描绘。当时人眼对色彩的区分程度是不一样的,比方在某些色彩上经过128或许64色来描绘,肉眼就现已无法区分,那么在这个信息里面咱们就能够做紧缩,这个是有损紧缩。

无损紧缩

无损紧缩是只对冗余的数据进行处理,然后能够完全恢复数据。

图片格局

  • Heic
  • Webp
  • jpg
  • png
  • Bmp
  • Svg

以上是常见的图片格局,其中bmp是位图格局,svg是矢量图格局,jpg是有损紧缩,png是无损紧缩,heic和webp是干流的高紧缩比图片格局

基准对比

阿里云OSS图片格局转化后的测试

cdn.poizon.com/node-common…

Data Size Image Size
原图(png) 97KB 1041KB
heic 10KB 1041KB
webp 32KB 1041KB

cdn.poizon.com/node-common…

Data Size Image Size
原图(png) 11MB 390MB
webp 1MB 292MB
jpg 7MB 390MB
png 10MB 390MB
Data Size Image Size
原图(png) 11MB 390MB
webp 1MB 292MB
jpg 7MB 390MB
png 10MB 390MB

相同的图片,不同的图片格局,在data size,image size,图片明晰度等多个维度上都有差异

图片编码/解码

图片紧缩是怎么做到的呢?在紧缩那段咱们说到有损紧缩是经过剔除肉眼不灵敏的色彩信息来处理,那么无损紧缩是怎么做的呢?

霍夫曼编码

简略来说,便是把重复最多的信息运用最少的位数来描绘。

例如,在英文中,e的呈现机率最高,而z的呈现概率则最低。当运用霍夫曼编码对一篇英文进行紧缩时,e极有可能用一个比特来表示,而z则可能花去25个比特(不是26)。用普通的表示办法时,每个英文字母均占用一个字节,即8个比特。二者相比,e运用了一般编码的1/8的长度,z则运用了3倍多。倘若咱们能完成对于英文中各个字母呈现概率的较精确的预算,就能够大幅度进步无损紧缩的份额。

解码便是相反的进程,把运用最少位数信息描绘的信息还原成之前的信息。

图片流程

iOS - 理解图片

iOS图片加载流程

iOS - 理解图片

图片源

iOS - 理解图片

Assets Catalog

图片位于assets catalog中有多项优化

  • 优化名称和特征查询
  • 更好的buffer缓存
  • 针对设备的瘦身
  • 矢量优化

加载办法

ImageNamed

会将图片资源缓存进入内存中

// Assets.xcassets内资源只能用过此办法调用
UIImage(named: "图片名")  

Bundle

Application/Framework bundle中的图片

加载办法

imageWithContentsOfFile
// 获取目录内图片文件时
Bundle.main.path(forResource: "文件名", ofType: "png")
UIImage(contentsOfFile: "文件路径")

Documents/Caches Directories

加载办法

if let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
    let imageURL = documentsDirectory.appendingPathComponent("myImage.jpg")
    do {
        let imageData = try Data(contentsOf: imageURL)
        guard let image = UIImage(data: imageData) else {
            print("Error loading image")
            return
        }
        // Do something with the image
    } catch {
        print("Error loading image: (error)")
    }
}

Network

经过网络加载的图片,通常运用目标存储服务OSS,并运用CDN做加速

OSS

OSS能够设置url参数对图片进行一些处理,常见的例如图片缩放、质量改换、格局转化。图片处理操作是同步处理,处理之后会缓存起来。

以阿里云OSS为例,还能够履行图片缩放、图片水印、自定义裁剪、质量改换、格局转化、获取信息、自适应方向、内切圆、索引切开、圆角矩形、含糊作用、旋转、渐进显现、获取图片主色调、亮度、锐化、对比度等操作。

图片处理URL,自定义裁剪缩放的参数运用URL Query的办法

oss-console-img-demo-cn-hangzhou.oss-cn-hangzhou.aliyuncs.com/example.jpg…

图片解码制作

UIkit

CGContextDrawImage


CGRect imageRect = CGRectMake(0.0f, 0.0f, CGImageGetWidth(self.image.CGImage), CGImageGetHeight(self.image.CGImage));
  CGFloat imageScale = self.view.frame.size.width/imageRect.size.width;
  imageRect.size = CGSizeMake(imageRect.size.width * imageScale, imageRect.size.height * imageScale);
  UIGraphicsBeginImageContext(imageRect.size);
  CGContextRef context = UIGraphicsGetCurrentContext();
  CGContextSaveGState(context);
  CGContextTranslateCTM(context, 0, imageRect.size.height);
  CGContextScaleCTM(context, 1.0, -1.0);
  CGContextDrawImage(context, imageRect, self.image.CGImage);
  CGContextRestoreGState(context);
  UIImage *handledImage = UIGraphicsGetImageFromCurrentImageContext();
  UIGraphicsEndImageContext();
  self.imageView.image = handledImage;

Core Graphics

ImageI/O

import ImageIO
if let url = Bundle.main.url(forResource: "myImage", withExtension: "png") {
    do {
        let options: [CFString:Any] = [
            kCGImageSourceCreateThumbnailFromImageAlways: true,
            kCGImageSourceThumbnailMaxPixelSize: 1000
        ]
        let imageData = try Data(contentsOf: url)
        guard let imageSource = CGImageSourceCreateWithData(imageData as CFData, nil) else {
            print("Error creating image source")
            return
        }
        guard let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) else {
            print("Error creating image")
            return
        }
        // Do something with the downscaled CGImage
    } catch {
        print("Error loading image: (error)")
    }
}

Core Image

import CoreImage
if let url = Bundle.main.url(forResource: "myImage", withExtension: "png") {
    do {
        let imageData = try Data(contentsOf: url)
        let ciImage = CIImage(data: imageData)
        // Do something with the CIImage
    } catch {
        print("Error loading image: (error)")
    }
}

vImage

import Accelerate.vImage
if let url = Bundle.main.url(forResource: "myImage", withExtension: "png") {
    do {
        let imageData = try Data(contentsOf: url)
        let imageSource = try CGImageSource(data: imageData as CFData)
        guard let image = imageSource?.cgImage(at: 0, options: nil) else {
            print("Error loading image")
            return
        }
        var format = vImage_CGImageFormat(bitsPerComponent: 8, bitsPerPixel: 32, colorSpace: nil, bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.first.rawValue), version: 0, decode: nil, renderingIntent: .defaultIntent)
        var buffer = vImage_Buffer()
        defer {
            free(buffer.data)
        }
        vImageBuffer_InitWithCGImage(&buffer, &format, nil, image, numericCast(kvImageNoFlags))
        // Do something with the vImage buffer
    } catch {
        print("Error loading image: (error)")
    }
}

高清大图瓦片加载

当需要加载例如地图和单反直出的高清高分辨率的大图时,而直接运用downsampling加载整张图片会选取和减少图片像素,下降图片质量。

此刻咱们需要运用瓦片式的加载完好图片,瓦片式加载会根据可视区域和缩放的份额,分区域的加载不同缩放下经过downsampling的瓦片。

并且在Scrollview缩放的进程中,内存会收回开释。

CATiledLayer


#import "DuImageBrowserTiledView.h"
#import <QuartzCore/QuartzCore.h>
@interface DuImageBrowserTiledView() {
  CGFloat imageScale;
  CGRect imageRect;
}
@end
@implementation DuImageBrowserTiledView
+ (Class)layerClass {
  return [CATiledLayer class];
}
- (id)initWithFrame: (CGRect)frame
       image: (UIImage *)image
       scale: (CGFloat)scale {
  if ((self = [super initWithFrame:frame])) {
    self.image = image;
    imageRect = CGRectMake(0.0f, 0.0f, CGImageGetWidth(image.CGImage), CGImageGetHeight(image.CGImage));
    imageScale = scale;
    CATiledLayer *tiledLayer = (CATiledLayer *)[self layer];
    tiledLayer.levelsOfDetail = 4;
    tiledLayer.levelsOfDetailBias = 4;
    tiledLayer.tileSize = CGSizeMake(512.0, 512.0);
  }
  return self;
}
- (void)drawRect: (CGRect)rect {
  CGContextRef context = UIGraphicsGetCurrentContext();
  CGContextSaveGState(context);
  CGContextScaleCTM(context, imageScale, imageScale);
  CGContextTranslateCTM(context, 0, imageRect.size.height);
  CGContextScaleCTM(context, 1.0, -1.0);
  CGContextDrawImage(context, imageRect, self.image.CGImage);
  CGContextRestoreGState(context);
}
@end

装备

levelsOfDetail

该layer保护的层级数目,每一个层级的分辨率是上一层级的二分之一

levelsOfDetailBias

layer的扩大等级重绘设置,每一个层级分辨率是后面层级的两倍

tileSize

瓦片的最大尺寸

内存机制

iOS - 理解图片

Buffer通常用于处理不同速率间的传输,运用一片接连的内存空间,一系列内部结构相同、巨细相同的元素组成的内存区域。

iOS - 理解图片

在Image Loading进程中有三种 Buffer

  • Data Buffer
  • Image Buffer
  • Frame Buffer

Data Buffer

Data Buffer 是存储在内存中的原始数据,图画能够运用不同的格局保存,如 jpg、png。Data Buffer 的信息不能用来描绘图画的像素信息。

Image Buffer

Image Buffer 是图画在内存中的存在办法,其中每个元素描绘了一个像素点。Image Buffer 的巨细和图画的巨细成正比。

Frame Buffer

Frame Buffer 和 Image Buffer 内容相同,不过其存储在 vRAM(video RAM)中,而 Image Buffer 存储在 RAM 中。

而解码便是从 Data Buffer 生成 Image Buffer 的进程。Image Buffer 会上传到 GPU 成为 Frame Buffer,GPU 以每秒60次的速度运用 Frame Buffer 更新屏幕。

缓存机制

干流的三方图片加载库,普遍采用内存和磁盘两级缓存,LRU的缓存淘汰策略,以及Cache Size,Expiration Time等装备

因为网络加载耗时在图片从加载解码到渲染的整体耗时中占比过大,所以设计更好的缓存机制,提高缓存命中率这个重要目标也特别重要

因为OSS会运用Url Query的办法修正URL,假如缓存仍以完好的URL作为存取的key的话,一方面会导致缓存命中率的下降。

另一方面,对于相同资源的图片,假如仅是分辨率不同,想要复用近似的图片缓存还需要考虑实际的图片明晰度和兼容规模。例如example.jgp?size=100,100与example.jgp?size=200,300的图片的复用策略

引证

Image and Graphics Best Practices