概述
最近鄰插值是最簡單的插值方法。該方法不是根據某些加權標準來計算平均值,也不是根據複雜的規則生成中間值,而是根據目標影像的寬(高)與源影像的寬(高)比值,取源影像相對位置的畫素點作為目標畫素點的值。新的畫素值一定是原圖的某個畫素值。
假設我們要把一個2X2的小圖片拉伸到4X4的尺寸,如下所示
假設P1的座標為(Dx, Dy),原圖的寬、高分別為Sw、Sh,拉伸後的影像寬、高分別為Dw、Dh,我們需要求P1在原圖的座標(Sx, Sy)。則
由
Dx / Dw = Sx / Sw
Dy / Dh = Sy / Sh
得
Sx = Dx * (Sh / Dh)
Sy = Dy * (Sw / Dw)
代入
Dx = 0
Dy = 0
Sh = 2
Sw = 2
Dh = 4
Dw = 4
得P1對應原圖的座標
Sx = 0 * (2 / 4) = 0
Sy = 0 * (2 / 4) = 0
同理P2的座標為(0, 1),代入
得P2對應原圖的座標
Sx = 0 * (2 / 4) = 0
Sy = 1 * (2 / 4) = 0.5 // 去掉小數部分 為 0
複製程式碼
因此P1點對應原圖的座標為(0, 0),也就是P1的值為10,P2對應原圖的座標為(0, 0),因此P2的值也為10,以此類推
iOS平臺原生API實現
- Core Graphics框架
當我們用Core Graphics框架來重取樣圖片時,可以設定CGContext
的interpolationQuality
(插值質量級別)為None。這樣就會使用最近鄰插值演算法
class func resizedImage(at url: URL, for size: CGSize) -> UIImage? {
guard let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil),
let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil)
else {
return nil
}
let context = CGContext(data: nil,
width: Int(size.width),
height: Int(size.height),
bitsPerComponent: image.bitsPerComponent,
bytesPerRow: image.bytesPerRow,
space: image.colorSpace ?? CGColorSpace(name: CGColorSpace.sRGB)!,
bitmapInfo: image.bitmapInfo.rawValue)
// 設定插值質量為 none,也就是使用最近鄰插值演算法
context?.interpolationQuality = .none
context?.draw(image, in: CGRect(origin: .zero, size: size))
guard let scaledImage = context?.makeImage() else { return nil }
return UIImage(cgImage: scaledImage)
}
複製程式碼
Objective-C 裡面的用法則是
CGContextSetInterpolationQuality(context, kCGInterpolationNone);
複製程式碼
- CALayer提供的API
在CALayer中,提供了minificationFilter
(縮小圖片時使用的過濾器型別)和magnificationFilter
(放大圖片時使用的過濾器型別)兩個屬性,指定渲染CALayer中的content
屬性的內容時使用的過濾器型別。當需要縮小影像資料時使用minificationFilter
指定的過濾器,當需要拉大影像資料時使用magnificationFilter
指定的過濾器。預設兩個屬性的值都為 kCAFilterLinear
。其中kCAFilterNearest
就是使用最近鄰插值演算法的過濾器。
- kCAFilterLinear
- kCAFilterNearest
- kCAFilterTrilinear
我們可以如下設定UIImageView
中的layer
的對應屬性,當所設定的影像資料過大或者過小導致需要拉伸時,就會使用你指定的過濾器型別來重取樣圖片。但需要注意的是,影像大小和顯示的大小不一致,是會有效能隱憂的,如果在一個列表中需要顯示很多影像,而這些影像大小又和實際顯示大小不一致時,在滑動時可能會引起卡頓。一般都是在後臺手動重取樣影像後才設定到UIImageView
的,而下面這樣的設定重取樣影像都是在主執行緒做的。
imageView.layer.magnificationFilter = kCAFilterNearest;
複製程式碼
演算法實現
我在iOS平臺上,使用Objective-C語言實現這個演算法。首先需要獲取到圖片的RGBA資料,也叫RAW資料,這是一個一維陣列,但是在實際處理中我們需要以二維的思路來遍歷。
- 獲取圖片RGBA資料
UInt32* pixelData = (UInt32 *)calloc(width * height, sizeof(UInt32));
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(pixelData,
width,
height,
bitsPerComponent,
bytesPerRow,
colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgOriginalImage);
複製程式碼
- 簡單地迴圈遍歷目標/輸出影像中的所有畫素,通過將寬、高座標按
rowRatio
和colRatio
縮放,並去掉小數得到縮放索引值,從而來定址要複製的源畫素
static UInt32* scaleImageWithNearesNeighborInterpolation(UInt32* pixelData, int sourceWidth, int sourceHeight, int desWidth, int desHeight) {
// 寬和高的縮放常數
float rowRatio = ((float)sourceHeight) / ((float)desHeight);
float colRatio = ((float)sourceWidth) / ((float)desWidth);
UInt32* rgba = (UInt32 *)calloc(desWidth * desHeight, sizeof(UInt32));
int offset=0;
for(int i = 0; i < desHeight; ++i) {
// 如果用round則四捨五入,round(0.5) = 1
// 這裡用floor,floor(0.5) = 0
int srcRow = floor(((float)i)*rowRatio);
if(srcRow >= sourceHeight) {
srcRow = sourceHeight - 1;
}
for (int j = 0; j < desWidth; j++) {
int srcCol = floor(((float)j)*colRatio);
if(srcCol >= sourceWidth) {
srcCol = sourceWidth - 1;
}
rgba[offset] = pixelData[(srcRow * sourceWidth + srcCol)];
offset++;
}
}
return rgba;
}
複製程式碼
- 把新的rgba資料,轉換回UIImage
CGContextRef bitmapContext = CGBitmapContextCreate(
rgba,
width,
height,
bitsPerComponent,
bytesPerRow,
colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGImageRef cgImage = CGBitmapContextCreateImage(bitmapContext);
UIImage *newUIImage = [UIImage imageWithCGImage:cgImage];
複製程式碼
最終得到下面的效果對比圖