目录
前言
采用swiftui core graphics技术,与c#的gdi 绘图类似,具体概念不多说,毕竟我也是新手,本文主要展示效果图及代码,本文示例代码需要请拉到文末自取。
1、图片缩放
- 完全填充,变形压缩
- 将图像居中缩放截取
- 等比缩放
上面三个效果,放一起比较好对比,如下
原图 - 完全填充,变形压缩 - 居中缩放截取 - 等比缩放
- 第1张为原图
- 第2张为完全填充,变形压缩
- 第3张为图像居中缩放截取
- 第4张为等比缩放
示例中缩放前后的图片可导出
2、图片拼图
顾名思义,将多张图片组合成一张图,以下为多张美图原图:
多张美图原图
选择后,界面中预览:
界面中预览
导出拼图查看效果:
导出拼图
3、图片操作方法
最后上图片缩放、拼图代码:
import swiftui struct imagehelper { static let shared = imagehelper() private init() {} // nsview 转 nsimage func imagefromview(cview: nsview) -> nsimage? { // 从view、data、cgimage获取bitmapimagerep // nsbitmapimagerep *bitmap = [nsbitmapimagerep imagerepwithdata:data]; // nsbitmapimagerep *bitmap = [[[nsbitmapimagerep alloc] initwithcgimage:cgimage]; guard let bitmap: nsbitmapimagerep = cview.bitmapimagerepforcachingdisplay(in: cview.visiblerect) else { return nil } cview.cachedisplay(in: cview.visiblerect, to: bitmap) let image: nsimage = nsimage(size: cview.frame.size) image.addrepresentation(bitmap) return image; } // 保存图片到本地 func saveimage(image: nsimage, filename: string) -> bool { guard var imagedata = image.tiffrepresentation, let imagerep = nsbitmapimagerep(data: imagedata) else { return false } // [imagerep setsize:size]; // 只是打开图片时的初始大小,对图片本省没有影响 // jpg if(filename.hassuffix("jpg")) { let quality:nsnumber = 0.85 // 压缩率 imagedata = imagerep.representation(using: .jpeg, properties:[.compressionfactor:quality])! } else { // png imagedata = imagerep.representation(using: .png, properties:[:])! } do { // 写文件 保存到本地需要关闭沙盒 ---- 保存的文件路径一定要是绝对路径,相对路径不行 try imagedata.write(to: , options: .atomic) return true } catch { return false } } // 将图片按照比例压缩 // rate 压缩比0.1~1.0之间 func compressedimagedatawithimg(image: nsimage, rate: cgfloat) -> nsdata? { guard let imagedata = image.tiffrepresentation, let imagerep = nsbitmapimagerep(data: imagedata) else { return nil } guard let data: data = imagerep.representation(using: .jpeg, properties:[.compressionfactor:rate]) else { return nil } return data as nsdata; } // 完全填充,变形压缩 func resizeimage(sourceimage: nsimage, forsize size: nssize) -> nsimage { let targetframe: nsrect = nsmakerect(0, 0, size.width, size.height); let sourceimagerep: nsimagerep = sourceimage.bestrepresentation(for: targetframe, context: nil, hints: nil)! let targetimage: nsimage = nsimage(size: size) targetimage.lockfocus() sourceimagerep.draw(in: targetframe) targetimage.unlockfocus() return targetimage; } // 将图像居中缩放截取targetsize func resizeimage1(sourceimage: nsimage, forsize targetsize: cgsize) -> nsimage { let imagesize: cgsize = sourceimage.size let width: cgfloat = imagesize.width let height: cgfloat = imagesize.height let targetwidth: cgfloat = targetsize.width let targetheight: cgfloat = targetsize.height var scalefactor: cgfloat = 0.0 let widthfactor: cgfloat = targetwidth / width let heightfactor: cgfloat = targetheight / height scalefactor = (widthfactor > heightfactor) ? widthfactor : heightfactor // 需要读取的源图像的高度或宽度 let readheight: cgfloat = targetheight / scalefactor let readwidth: cgfloat = targetwidth / scalefactor let readpoint: cgpoint = cgpoint(x: widthfactor > heightfactor ? 0 : (width - readwidth) * 0.5, y: widthfactor < heightfactor ? 0 : (height - readheight) * 0.5) let newimage: nsimage = nsimage(size: targetsize) let thumbnailrect: cgrect = cgrect(x: 0, y: 0, width: targetsize.width, height: targetsize.height) let imagerect: nsrect = nsrect(x: readpoint.x, y: readpoint.y, width: readwidth, height: readheight) newimage.lockfocus() sourceimage.draw(in: thumbnailrect, from: imagerect, operation: .copy, fraction: 1.0) newimage.unlockfocus() return newimage; } // 等比缩放 func resizeimage2(sourceimage: nsimage, forsize targetsize: cgsize) -> nsimage { let imagesize: cgsize = sourceimage.size let width: cgfloat = imagesize.width let height: cgfloat = imagesize.height let targetwidth: cgfloat = targetsize.width let targetheight: cgfloat = targetsize.height var scalefactor: cgfloat = 0.0 var scaledwidth: cgfloat = targetwidth var scaledheight: cgfloat = targetheight var thumbnailpoint: cgpoint = cgpoint(x: 0.0, y: 0.0) if __cgsizeequaltosize(imagesize, targetsize) == false { let widthfactor: cgfloat = targetwidth / width let heightfactor: cgfloat = targetheight / height // scale to fit the longer scalefactor = (widthfactor > heightfactor) ? widthfactor : heightfactor scaledwidth = ceil(width * scalefactor) scaledheight = ceil(height * scalefactor) // center the image if (widthfactor > heightfactor) { thumbnailpoint.y = (targetheight - scaledheight) * 0.5 } else if (widthfactor < heightfactor) { thumbnailpoint.x = (targetwidth - scaledwidth) * 0.5 } } let newimage: nsimage = nsimage(size: nssize(width: scaledwidth, height: scaledheight)) let thumbnailrect: cgrect = cgrect(x: thumbnailpoint.x, y: thumbnailpoint.y, width: scaledwidth, height: scaledheight) let imagerect: nsrect = nsrect(x: 0.0, y:0.0, width: width, height: height) newimage.lockfocus() sourceimage.draw(in: thumbnailrect, from: imagerect, operation: .copy, fraction: 1.0) newimage.unlockfocus() return newimage; } // 将图片压缩到指定大小(kb) func compressimgdata(imgdata: nsdata, toaimkb aimkb: nsinteger) -> nsdata? { let aimrate: cgfloat = cgfloat(aimkb * 1000) / cgfloat(imgdata.length) let imagerep: nsbitmapimagerep = nsbitmapimagerep(data: imgdata as data)! guard let data: data = imagerep.representation(using: .jpeg, properties:[.compressionfactor:aimrate]) else { return nil } print("数据最终大小:\(cgfloat(data.count) / 1000), 压缩比率:\(cgfloat(data.count) / cgfloat(imgdata.length))") return data as nsdata } // 组合图片 func jointedimagewithimages(imgarray: [nsimage]) -> nsimage { var imgw: cgfloat = 0 var imgh: cgfloat = 0 for img in imgarray { imgw = img.size.width; if (imgh < img.size.height) { imgh = img.size.height; } } print("size : \(nsstringfromsize(nssize(width: imgw, height: imgh)))") let togetherimg: nsimage = nsimage(size: nssize(width: imgw, height: imgh)) togetherimg.lockfocus() let imgcontext: cgcontext? = nsgraphicscontext.current?.cgcontext var imgx: cgfloat = 0 for imgitem in imgarray { if let img = imgitem as? nsimage { let imageref: cgimage = self.getcgimagereffromnsimage(image: img)! imgcontext?.draw(imageref, in: nsrect(x: imgx, y: 0, width: img.size.width, height: img.size.height)) imgx = img.size.width; } } togetherimg.unlockfocus() return togetherimg; } // nsimage转cgimageref func getcgimagereffromnsimage(image: nsimage) -> cgimage? { let imagedata: nsdata? = image.tiffrepresentation as nsdata? var imageref: cgimage? = nil if(imagedata != nil) { let imagesource: cgimagesource = cgimagesourcecreatewithdata(imagedata! as cfdata, nil)! imageref = cgimagesourcecreateimageatindex(imagesource, 0, nil) } return imageref; } // cgimage 转 nsimage func getnsimagewithcgimageref(imageref: cgimage) -> nsimage? { return nsimage(cgimage: imageref, size: nssize(width: imageref.width, height: imageref.height)) // var imagerect: nsrect = nsrect(x: 0, y: 0, width: 0, height: 0) // // var imagecontext: cgcontext? = nil // var newimage: nsimage? = nil // // imagerect.size.height = cgfloat(imageref.height) // imagerect.size.width = cgfloat(imageref.width) // // // create a new image to receive the quartz image data. // newimage = nsimage(size: imagerect.size) // // newimage?.lockfocus() // // get the quartz context and draw. // imagecontext = nsgraphicscontext.current?.cgcontext // imagecontext?.draw(imageref, in: imagerect) // newimage?.unlockfocus() // // return newimage; } // nsimage转ciimage func getciimagewithnsimage(image: nsimage) -> ciimage?{ // convert nsimage to bitmap guard let imagedata = image.tiffrepresentation, let imagerep = nsbitmapimagerep(data: imagedata) else { return nil } // create ciimage from imagerep let ciimage: ciimage = ciimage(bitmapimagerep: imagerep)! // create affine transform to flip ciimage let affinetransform: nsaffinetransform = nsaffinetransform() affinetransform.translatex(by: 0, yby: 128) affinetransform.scalex(by: 1, yby: -1) // create cifilter with embedded affine transform let transform:cifilter = cifilter(name: "ciaffinetransform")! transform.setvalue(ciimage, forkey: "inputimage") transform.setvalue(affinetransform, forkey: "inputtransform") // get the new ciimage, flipped and ready to serve let result: ciimage? = transform.value(forkey: "outputimage") as? ciimage return result; } }
4、示例代码
界面布局及效果展示代码
import swiftui struct testimagedemo: view { @state private var sourceimagepath: string? @state private var sourceimage: nsimage? @state private var sourceimagewidth: cgfloat = 0 @state private var sourceimageheight: cgfloat = 0 @state private var resizeimage: nsimage? @state private var resizeimagewidth: string = "250" @state private var resizeimageheight: string = "250" @state private var resize1image: nsimage? @state private var resize1imagewidth: string = "250" @state private var resize1imageheight: string = "250" @state private var resize2image: nsimage? @state private var resize2imagewidth: string = "250" @state private var resize2imageheight: string = "250" @state private var joinimage: nsimage? var body: some view { geometryreader { reader in vstack { hstack { button("选择展示图片缩放", action: self.choiceresizeimage) button("选择展示图片拼图", action: self.choicejoinimage) spacer() } hstack { vstack { if let simage = sourceimage { section(header: text("原图")) { image(nsimage: simage) .resizable().aspectratio(contentmode: .fit) .frame(width: reader.size.width / 2) text("\(self.sourceimagewidth)*\(self.sourceimageheight)") button("导出", action: { self.saveimage(image: simage) }) } } if let simage = self.joinimage { section(header: text("拼图")) { image(nsimage: simage) .resizable().aspectratio(contentmode: .fit) .frame(width: reader.size.width) button("导出", action: { self.saveimage(image: simage) }) } } } vstack { section(header: text("完全填充,变形压缩")) { vstack { section(header: text("width:")) { textfield("width", text: self.$resizeimagewidth) } section(header: text("height:")) { textfield("height", text: self.$resizeimageheight) } if let simage = resizeimage { image(nsimage: simage) text("\(self.resizeimagewidth)*\(self.resizeimageheight)") button("导出", action: { self.saveimage(image: simage) }) } } } } vstack { section(header: text("将图像居中缩放截取")) { vstack { section(header: text("width:")) { textfield("width", text: self.$resize1imagewidth) } section(header: text("height:")) { textfield("height", text: self.$resize1imageheight) } if let simage = resize1image { image(nsimage: simage) text("\(self.resize1imagewidth)*\(self.resize1imageheight)") button("导出", action: { self.saveimage(image: simage) }) } } } } vstack { section(header: text("等比缩放")) { vstack { section(header: text("width:")) { textfield("width", text: self.$resize2imagewidth) } section(header: text("height:")) { textfield("height", text: self.$resize2imageheight) } if let simage = resize2image { image(nsimage: simage) text("\(self.resize2imagewidth)*\(self.resize2imageheight)") button("导出", action: { self.saveimage(image: simage) }) } } } } spacer() } spacer() } } } private func choiceresizeimage() { let result: (fail: bool, url: [url?]?) = dialogprovider.shared.showopenfiledialog(title: "", prompt: "", message: "选择图片", directoryurl: , allowedfiletypes: ["png", "jpg", "jpeg"]) if result.fail { return } if let urls = result.url, let url = urls[0] { self.sourceimagepath = url.path self.sourceimage = nsimage(contentsof: ) self.sourceimagewidth = (self.sourceimage?.size.width)! self.sourceimageheight = (self.sourceimage?.size.height)! if let resizewidth = int(self.resizeimagewidth), let resizeheight = int(self.resizeimageheight) { self.resizeimage = imagehelper.shared.resizeimage(sourceimage: self.sourceimage!, forsize: cgsize(width: resizewidth, height: resizeheight)) } if let resize1width = int(self.resize1imagewidth), let resize1height = int(self.resize1imageheight) { self.resize1image = imagehelper.shared.resizeimage1(sourceimage: self.sourceimage!, forsize: cgsize(width: resize1width, height: resize1height)) } if let resize2width = int(self.resize2imagewidth), let resize2height = int(self.resize2imageheight) { self.resize2image = imagehelper.shared.resizeimage1(sourceimage: self.sourceimage!, forsize: cgsize(width: resize2width, height: resize2height)) } } } private func choicejoinimage() { let result: (fail: bool, url: [url?]?) = dialogprovider.shared.showopenfiledialog(title: "", prompt: "", message: "选择图片", directoryurl: , allowedfiletypes: ["png", "jpg", "jpeg"], allowsmultipleselection: true) if result.fail { return } if let urls = result.url { var imgs: [nsimage] = [] for url in urls { if let filepath = url?.path { imgs.append(nsimage(contentsof: )!) } } if imgs.count > 0 { self.joinimage = imagehelper.shared.jointedimagewithimages(imgarray: imgs) } } } private func saveimage(image: nsimage) { let result: (isopenfail: bool, url: url?) = dialogprovider.shared.showsavedialog( title: "选择图片存储路径", directoryurl: , prompt: "", message: "", allowedfiletypes: ["png"] ) if result.isopenfail || result.url == nil || result.url!.path.isempty { return } let exportimagepath = result.url!.path _ = imagehelper.shared.saveimage(image: image, filename: exportimagepath) nsworkspace.shared.activatefileviewerselecting([]) } } struct testimagedemo_previews: previewprovider { static var previews: some view { testimagedemo() } }
5、结尾
所有代码已贴,并且代码已上传github,见下面备注。
本文示例代码:https://github.com/dotnet9/mactest/blob/main/src/macos_test/macos_test/testimagedemo.swift
参考文章标题:《mac图像nsimage缩放、组合、压缩及ciimageref和nsimage转换处理》
参考文章链接:https://www.freesion.com/article/774352759/