KUVAN TUONTI
NÄYTÖLLE
KUVAN TUONTI NÄYTÖLLE LAADUKKAANA - FUNKTIOT
Seuraavassa on muutama omissa sovelluksissani käyttämääni funktiota,
jotka pitävät kuvan laadun hyvänä. Useat Xcode-kontrollit tekevät
kuvista epäteräviä, jos antaa niiden skaalata kuvia.
Näihin funktioihin kannattaa lisätä sovellukseen sopivalla tavalla
ilmoituksia miksi homma epäonnistui. Rivien säästämiseksi tässä on
nyt epäonnistumispaikoissa vain esimerkiksi '{ return nil }'
Jos tavallisen kameran tekemän kuvan haluaa näytölle
pienempikokoisena, kuten normaalitilanne on, niin seuraavassa on
funktio, joka pienentää ja terävöittää kuvan. Ilman terävöitystä
kuva usein näyttää yllättävän epäterävältä. Jäljempänä oleva
kokoaKuva6() tekee riittävän terävän kuvan silloin jos lopputuloksen
tarvitsee olla thumbnail-kokoa esim. pienempi kuin 200x150 pikseliä.
Tämä seuraava funktio hyödyntää coreImage-kirjastoa, eikä se ota
automaattisesti huomioon kuvan laittamista oikeaan asentoon
(Orientation) kuten muut alempana olevat funktiot. Siksi tässä on
erikseen Orientation-tiedon hakeva funktio, joka toimii ainakin
iPhonella ja iPadilla otetuilla valokuvilla.
Huomaa, että coreImage tekee osan työstään hyödyntäen
näytönohjainta, ja sitä varten taustalla on käytössä "Metal API".
Kun sovellusta testaa ja ajaa Xcodessa, niin Metal API -kutsujen
ennakkotarkastus on käytössä, kun taas Xcode:n ulkopuolella se ei
ole käytössä ja suorituskin on nopeampaa.
Mojave-käyttöjärjestelmässä ennakkotarkastuksessa on bugi ja tulee
vaikeasti tulkittavia virheitä, joiden ilmoituksissa on termejä
"MTLDebugComputeCommandEncoder... failed assertion", ja "Thread 1:
signal SIGABRT". Tästä selviää ottamalla ennakkotarkastuksen pois
käytöstä: valitse Xcoden menusta:
Product -> Scheme -> Edit Scheme -> Options -> Run ->
Metal API Validation Disabled
Funktio skaalaaTeravoita() soveltuu parhaiten tilanteeseen, jossa
tuodaan näytölle yksittäinen kuva. Funktion sisällä muodostettavat
CIContext() ja CIFilter() ovat raskaita ja jos ikkunassa esimerkiksi
haluaa suurentaa ja pienentään kuvaa ikkunaa suurentamalla ja
pienentämällä, niin kannattaa osa funktion muuttujista ottaa ulos
luokkamuuttujiksi.
Käyttöesimerkki:
//let tila =
CGSize(width: ww, height: hh) // tila johon kuva sijoitetaan
//let uusima = skaalaaTeravoita(kuvapolkuz, koko: tila)
//imageviewOma.setFrameSize(uusima!.size)
//imageviewOma.image = uusima!
class func skaalaaTeravoita(_ polkuz: String, koko: CGSize) ->
NSImage?
{
let urli = URL(fileURLWithPath: polkuz) // koko
polku
let ciimage0 = CIImage(contentsOf: urli) // nil
tarkistettava
if ciimage0 == nil { return nil }
let orient = Meka.haeOrient(polkuz) //
haetaan kuvasta tieto Orientation
let context = CIContext(options:
[.useSoftwareRenderer : false])
let ciimage = ciimage0!.oriented(orient) //
käännetään kuva oikein päin
let alkuw = ciimage.extent.width //
CGFloat
let alkuh = ciimage.extent.height
let alkuswh = alkuw / alkuh // alkuperäiskuvan
sivusuhde
let w = koko.width // tähän tilaan pitää
mahtua (CGFloat)
let h = koko.height
let suhde = w / h //
halutun pikkukuvan sivusuhde
let uusiw = (alkuswh <= suhde) ? h * alkuswh
: w
let uusih = (alkuswh <= suhde) ? h : w /
alkuswh
// filtterit ketjutetaan (output on seuraavan input)
// suoritus tapahtuu optimaalisesti GPU:ssa
let scaleFilter =
CIFilter(name:"CILanczosScaleTransform")!
scaleFilter.setValue(ciimage, forKey:
kCIInputImageKey)
scaleFilter.setValue(Double(uusiw / w), forKey:
kCIInputScaleKey)
scaleFilter.setValue(suhde, forKey:
kCIInputAspectRatioKey)
let sharpFilter = CIFilter(name: "CIUnsharpMask")!
sharpFilter.setValue(scaleFilter.outputImage!,
forKey: kCIInputImageKey)
sharpFilter.setValue(2.0, forKey: "inputIntensity")
sharpFilter.setValue(1.0, forKey: "inputRadius")
let ouCIIma2 = sharpFilter.outputImage
let ouCGIma2 = context.createCGImage(ouCIIma2!, from:
ouCIIma2!.extent)
let uutila = CGSize(width: uusiw, height: uusih)
let nsima = NSImage(cgImage: ouCGIma2!, size: uutila)
return nsima
}
Seuraava funktio
hakee kuvatiedoston metatiedoista Orientation -tiedon.
Outputtina löytynyt Orientation, ja jos ei löydy, niin outputtina
.up (normaaliasento)
Testattu kameroilla iPhone ja iPad.
Käyttöesimerkki:
// let ori = haeOrient(kuvapolkuz)
// let ciimage = ciimage0!.oriented(orient) // käännetään
kuva oikein päin
class func haeOrient(_ polkuz: String) ->
CGImagePropertyOrientation
{
if polkuz.isEmpty { return
CGImagePropertyOrientation.up }
let fm = FileManager()
if !fm.fileExists(atPath: polkuz) { return
CGImagePropertyOrientation.up }
let urli = URL(fileURLWithPath: polkuz) // koko
polku
// Tämä kurkistaa levyllä metatietoja, eikä lataa
kuvaa muistiin:
let cgref = CGImageSourceCreateWithURL(urli as CFURL,
nil)
if cgref == nil { return
CGImagePropertyOrientation.up }
let imaprop2: NSDictionary =
CGImageSourceCopyPropertiesAtIndex(cgref!, 0, nil)!
let suunta: Int? = imaprop2["Orientation"] as! Int?
let ori = (suunta != nil) ? suunta! : EIOO
var orient: CGImagePropertyOrientation = .right
switch ori // orientation-muunnos
macOS-ohjelmakirjastoja varten
{
case 1:
orient =
CGImagePropertyOrientation.up
case 6:
orient =
CGImagePropertyOrientation.right
case 3:
orient =
CGImagePropertyOrientation.down
case 8:
orient =
CGImagePropertyOrientation.left
default:
orient =
CGImagePropertyOrientation.up
}
return orient
}
/*
Tämä funktio tuottaa aina odotusten kokoisen kuvan esimerkiksi
NSImageView:lle, ja tulos on laadukas. Inputtina joko muistissa
oleva kuvatiedostorakenne (blobx), tai kuvatiedosto (tayspolkuz).
Funktio käyttää cgImage-funktioita. Kutsussa annetaan haluttu uuden
kuvan koko.
Ei tehdä kuvasta suurennosta, eli jos haluttu kuva on alkuperäistä
suurempi, funktio antaa alkuperäisen kuvan (koodia on helppo
täydentää).
Thumbnail-kuvan tekeminen tietokantaan vietäväksi:
let ima =
kokoaKuva6(tayspolkuz, blobx: nil, wid: 102, hei: 80)
let blob = ima!.tiffRepresentation
let blob2 = NSBitmapImageRep(data: blob!)!.representation(using:
.jpeg, properties: [NSBitmapImageRep.PropertyKey.compressionFactor
: 0.6])
*/
//------------------------------------
class func kokoaKuva6(_ polkuz: String, blobx: Data?, wid w: Int,
hei h: Int) -> NSImage?
{
if polkuz.isEmpty && blobx == nil {
return nil }
if !polkuz.isEmpty && blobx != nil {
return nil }
var ww = 0 // kuvan 'polkuz' mitat pixeliä
var hh = 0
var imasorsa: CGImageSource? = nil //
kuvalähde
if !polkuz.isEmpty
{
let fm = FileManager()
if !fm.fileExists(atPath:
polkuz) { return nil }
let failiUrl = URL(fileURLWithPath:
polkuz, isDirectory: false)
imasorsa =
CGImageSourceCreateWithURL(failiUrl.absoluteURL as CFURL, nil)
}
if blobx != nil
{
imasorsa =
CGImageSourceCreateWithData(blobx! as CFData, nil)
}
if imasorsa == nil { return nil }
let imaprop =
CGImageSourceCopyPropertiesAtIndex(imasorsa!, 0, nil)
if imaprop == nil { return nil }
let imaprop2: NSDictionary = imaprop!
ww = imaprop2["PixelWidth"] as! Int
hh = imaprop2["PixelHeight"] as! Int
if ww == 0 || hh == 0 { return nil }
var wux = 0 // lopputuloskuvan suurin
pikselimitta
if (ww <= w && hh <= h) || blobx
!= nil // onko tiedoston kuva haluttua pienempi
{
wux = (ww > hh) ? ww :
hh // annetaan vain alkuperäinen kuva
}
else // skaalataan alkuperäiskuva
pienemmäksi
{
let suw = CGFloat(w) / CGFloat(ww)
let suh = CGFloat(h) / CGFloat(hh)
let suhde = min(suw, suh)
let wx = (ww > hh) ? ww : hh
wux = Int(round(suhde *
CGFloat(wx)))
}
let thumbnailOptions =
[
String(kCGImageSourceCreateThumbnailFromImageAlways): true,
String(kCGImageSourceCreateThumbnailWithTransform): true,
String(kCGImageSourceThumbnailMaxPixelSize): wux
] as [String : Any]
if let thumbnailRef =
CGImageSourceCreateThumbnailAtIndex(imasorsa!, 0, thumbnailOptions
as CFDictionary?)
{
return NSImage(cgImage:
thumbnailRef, size: NSSize.zero)
}
return nil
}
/*
Tämä funktio tuottaa aina odotusten kokoisen kuvan esimerkiksi
NSImageView:lle, mutta tulos on joskus hieman heikompi kuin edellä
johtuen ilmeisesti kirjastofunktioiden pakkaus- tms. oletuksista.
Tätä voi käyttää esimerkiksi silloin, jos katselukuvan laadulla ei
ole suurta merkitystä. Inputtina joko muistissa oleva
kuvatiedostorakenne (blobx), tai kuvatiedosto (tayspolkuz). Funktio
lukee NSImageRept-rakenteet ja tekee NSImagen jossa vain yksi
NSImageRept. Vaikka lähtötiedostossakin olisi vain yksi, niin
tuloksena oleva NSImage näkyy oikein NSImageView:llä, vaikka
funktiolla NSImage.init(contentsOfFile: tayspolkuz) tehty ei
näkyisi.
*/
//------------------------------------
class func kokoaKuva2(_ blobx: Data?, polkuz: String) ->
NSImage?
{
if polkuz.isEmpty && blobx == nil {
return nil }
if !polkuz.isEmpty && blobx != nil {
return nil }
let reptit: [NSImageRep]
if !polkuz.isEmpty
{
let fm = FileManager()
if !fm.fileExists(atPath:
polkuz) { return nil }
reptit =
NSBitmapImageRep.imageReps(withContentsOfFile: polkuz)!
}
else
{
let ima = NSImage.init(data:
blobx!)
if ima == nil { return nil }
reptit = ima!.representations
}
var w = 0
var h = 0
let oli = reptit.count
if oli == 0 { return nil }
var ivali = 0
// etsitään suurinta kuvaa vastaava imagerep ja se
otetaan
for i in 0..<oli
{
if reptit[i].pixelsWide > w
{
w =
reptit[i].pixelsWide
h =
reptit[i].pixelsHigh
ivali = i
}
}
let imarepuusi = reptit[ivali] // otetaan vain
suurimman kuvan imagerep
let imax = NSImage.init(size: CGSize(width: w,
height: h))
imax.addRepresentation(imarepuusi)
return imax
}
/*
Tämän funktion inputtina on NSImage ja siitä halutaan skaalata
toisenkokoinen (suurempi tai
pienempi) kuva (NSImage), jonka koko annetaan funktion parametrina.
Tulos on laadukas.
Funktio käyttää cgImage-funktioita.
*/
//------------------------------------
class func kuvanKokomuutos(_ alkuima: NSImage?, w: CGFloat,
h: CGFloat) -> NSImage?
{
if alkuima == nil || w < 0.1 || h < 0.1 {
return nil }
let alkukoko = alkuima!.size
let suhde = w / h // halutun pikkukuvan sivusuhde
// alkuperäiskuvan sivusuhde:
let son = alkukoko.width / alkukoko.height
let uusiw = (son <= suhde) ? h * son : w
let uusih = (son <= suhde) ? h : w / son
let uuskoko = CGSize(width: floor(uusiw), height:
floor(uusih))
var fromrect = CGRect(x: 0, y: 0, width:
alkukoko.width, height: alkukoko.height)
// Cast the NSImage to a CGImage, and high
quality:
NSGraphicsContext.current()?.imageInterpolation =
NSImageInterpolation.high
let imageRef = alkuima!.cgImage(forProposedRect:
&fromrect, context: NSGraphicsContext.current, hints: nil)
return NSImage(cgImage: imageRef!, size: uuskoko)
}
--------------------------------
(sivua muokattu 14.11.2019)