Tehtävä 3 Kuva, Deadline 25.11. klo 18:00 Tässä tehtävässä tutustutaan kuvien muokkaamiseen, ulkoisten scala-kirjastojen käyttöön sekä verkkopalveluiden rajapintoihin. Tulet toteuttamaan ohjelman jolla voit muokata kuvia sekä bonustehtävänä julkaista niitä verkossa. Täydet pisteet voit siis saada tekemällä osatehtävät 1-4. Huomaa että bonustehtävä on perustehtäviä vaikeampi ja vaatii huomattavasti enemmän itsenäistä selvitystyötä. Ohjelmointi 1-kurssilta apua on tarjolla erityisesti luvuista: Luku 5.5: Silmukoita, vektoreita ja vaalit Luku 5.6: Taulukoita ja sisäkkäisyyttä Luku 7.3: Funktiot parametreina 1. Valmistelu Luo uusi Scala-projekti ja kopioi sinne valmiiksi annetun ImageFilterExercise-projektin sisältö. Jos projektissa näkyy virheilmoituksia, lisää projektikansiossa olevat.jar tiedostot scala-swing, scalajhttp sekä spray-json ohjelmasi "Build Pathiin" (oikea klikkaus -> Build Path, Add to Build Path). Scalajhttp ja spray-json kirjastoja tarvitaan vain bonustehtävässä, joten voit halutessasi jättää ne pois. Voit nyt ajaa ohjelman klikkaamalla view.scalaa ja Run as-> Scala Application. Syötä konsoliin haluamasi projektikansiossa sijaitsevan kuvatiedoston nimi. 2. Color-luokka ja sen kumppaniolio Color-apuluokan tarkoitus on helpottaa värien luomista ja muokkausta kuvaa käsiteltäessä. Luokassa on jo kaksi valmiiksi toteutettua metodia, tarkemmin konstruktoria eli metodit seuraavilla allekirjoituksilla def this(r: Int, g: Int, b: Int, a: Int) ja def this(argb: Int) Kuten huomaat, metodit ovat samannimisiä, mutta ne ottavat vastaan erilaiset parametrit. Tätä sanotaan kuormittamiseksi (overloading). Meidän tapauksessamme haluamme luoda Color-luokalle kaksi ylimääräistä konstruktoria, jotta väriolioita voitaisiin luoda hieman helpommin. Luotaessa Color-luokan instanssia voidaan siis välittää joko 1, 4 tai 5 parametria. Toisin kuin Javassa, Scalassa ylimääräisten konstruktoreiden täytyy toteutuksessaan kutsua ensimmäisenä alkuperäistä, eli tässä tapauksessa viiden parametrin konstruktoria, tai luokassa aikaisemmin määriteltyä toissijaista konstruktoria. Seurauksena jokainen konstruktori päätyy siis lopulta kutsumaan alkuperäistä konstruktoria. Voit lukea liksää esimerkiksi täältä (http://www.artima.com/pins1ed/functional-objects.html#6.7).
Yhden parametrin versiossa kokonaislukuarvo pitää sisällään kaikkien värikomponenttien ja läpinäkyvyyskomponentin arvot. Kukin komponentti on arvoltaan 0 ja 255 välillä, joten komponentin kuvaamiseen riittää 8 bittiä. Neljä komponenttia vievät yhteensä 32 bittiä, mikä on tarkalleen kokonaislukutyypin Int koko. Koska aivan aluksi on kutsuttava yleistä konstruktoria, yksittäisten komponenttien arvot asetetaan tämän jälkeen setcomponents metodilla. Vastaavasti neljän parametrin versiossa komponenttien arvot käsitellään erikseen ja kaikkien komponenttien tiedon sisältävä kokonaisluku asetetaan setint metodilla. Tämän lisäksi näissä konstruktoreissa pidetään huolta siitä, että komponentin arvoksi asetetaan sopiva arvo kutsumalla clamp metodia kullekin komponentille. Sinun tehtävänäsi on: Toteuttaa metodi, jonka avulla rajoitetaan parametrina välitetty luku välille [0, 255]. Toteuttaa metodi, joka erottelee yksittäisestä Int arvosta neljä värikomponenttia. Toteuttaa metodi, joka yhdistää neljästä värikomponentista kaiken väri-informaation yksittäiseen Int-arvoon. 2.1. Clamp-metodi Toteuta kumppaniolio Colorin metodi Clamp, jonka tulee rajoittaa sille parametrina välitetty arvo välille [0, 255] ja palauttaa tulos. Jos metodille välitetään esimerkiksi arvo 78, palauttaa metodi 78. Mikäli metodille välitetty arvo on alle 0, palautetaan 0 ja mikäli metodille välitetty arvo on enemmän kuin 255, palautetaan 255. 2.2. setint- ja setcomponent-metodit setint metodi luo luokan a, r, g ja b muuttujien pohjalta kokonaisluvun, joka sisältää kaikkien muuttujien tiedon ja tallentaa sen muuttujaan argb. Metodin toteuttamiseksi sinun tulee käsitellä kokonaislukuja bittitasolla. Voit tutustua erilaisiin bittioperaatioihin esimerkiksi täältä http://www.tutorialspoint.com/scala/scala_bitwise_operators.htm. Tarkoituksena on luoda 32-bittinen kokonaisluku tallennettavaksi muuttujaan argb käyttäen muuttujien a, r, g ja b kahdeksaa (8) ensimmäistä bittiä. Saat siirrettyä bittejä sopivaan sijaintiin << - operaattorilla ja yhdistettyjä luvut -operaattorilla. Katso alta esimerkki ja leiki REPLissä. scala> val r = 255 r: Int = 255 scala> val b = 80
b: Int = 80 scala> val shiftedr = r << 16 shiftedr: Int = 16711680 scala> r.tobinarystring res0: String = 11111111 scala> b.tobinarystring res1: String = 1010000 scala> shiftedr.tobinarystring res2: String = 111111110000000000000000 scala> shiftedr b res3: Int = 16711760 scala> res3.tobinarystring res4: String = 111111110000000001010000 setcomponents-metodi tekee periaatteessa edellisen käännettynä. Tässä metodissa käytä bittien siirtelyyn joko >>> tai >> -operaattoria. Näistä ensimmäinen korvaa shiftatessaan vasemmalle jäävät ylimääräiset bitit nolliksi, kun taas jälkimmäinen korvaa ne ykkösillä. scala> val i = -2402617 i: Int = -2402617 scala> i.tobinarystring res0: String = 11111111110110110101011011000111 scala> i >> 24 res1: Int = -1 scala> res1.tobinarystring res2: String = 11111111111111111111111111111111 scala> i >>> 24 res3: Int = 255 Saat valittua haluamiasi bittejä käsittelyyn tekemällä bittioperaation "ja" eli &. Esimerkiksi seuraavasti. scala> i >>> 16 res10: Int = 65499 scala> res10.tobinarystring res11: String = 1111111111011011
scala> res10 & 0xFF res12: Int = 219 scala> res12.tobinarystring res13: String = 11011011 2.3. Testaus Voit testata täydentämääsi Color-luokkaa esimerkiksi REPLissä. Muista lisätä aluksi pakkaus import komennolla. Voit nyt tallentaa värejä muuttujiin ja testata, että niihin tallentuvat arvot ovat oikeita. Esimerkiksi: scala> val c = new Color(100,-9,200,450) c: kierros3.color = kierros3.color@3571b748 scala> c.a res10: Int = 100 scala> c.r res11: Int = 0 scala> c.g res12: Int = 200 scala> c.b res13: Int = 255 scala> c.argb res14: Int = 1677773055 scala> c.argb.tobinarystring res15: String = 1100100000000001100100011111111 3. Filter-luokan yksinkertaiset suotimet Tässä vaiheessa tulet: Toteuttamaan metodin, jolla voidaan muokata kuva harmaasävyiseksi. Toteuttamaan metodin, jolla kuvan värit voidaan kääntää vastaväreiksi. Toteuttamaan metodin, jolla voidaan muokata kuvan valoisuutta. Totuttamaan kolme metodia, joilla kullakin voidaan säätää yhtä kuvan värikomponenttia. 3.1. Harmaasävy ja vastavärit Tutustu aluksi Image-luokkaan ja sen dokumentaatioon. Miten pääset käsiksi pikseleiden väridataan?
Toteuta metodi: def grayscale(img: Image) : Unit joka muuttaa kuvan harmaasävyiseksi. Jokaiselle kuvapisteelle lasketaan uusi arvo, jossa värikomponenttien (RGB) uudeksi arvoksi tulee kaikkien värikomponenttien (RGB) keskiarvo ja läpinäkyvyyskomponentti Alpha pysyy ennallaan. Älä muuta alkuperäisen kuvan dataa (sen ei pitäisi edes olla mahdollista) vaan kutsu Image luokan setimage metodia asettaaksesi kuvalle tieto muokatusta kuvadatasta. Toteuta myös metodi: def invert(img: Image) : Unit joka kääntää kuvan värin negatiiviseksi. Jokaiselle kuvapisteelle lasketaan uusi arvo, jossa värikomponenttien (RGB) uusi arvo määräytyy erotuslaskulla 255 - "värikomponentin arvo" ja läpinäkyvyyskomponentti Alpha pysyy ennallaan. Käytä jälleen Image luokan setimage metodia asettaaksesi kuvalle tieto sen muokatusta kuvadatasta. 3.2 Valoisuus Toteuta metodi: def lightness(factor: Float, img: Image) : Unit joka muuttaa kuvan valoisuutta parametrin factor mukaan. Kun kuvapisteitä kerrotaan ykköstä (1) pienemmillä arvoilla, kuva tummenee ja vastaavasti yhtä (1) suuremmat arvot vaalentavat kuvaa. Jokaiselle kuvapisteelle lasketaan uusi arvo, jossa värikomponenttien (RGB) uusi arvo määräytyy kertolaskulla "värikomponentin arvo" * factor. Läpinäkyvyyskomponentti Alpha pysyy ennallaan. Käytä jälleen Image luokan setimage metodia asettaaksesi kuvalle tieto sen muokatusta kuvadatasta. 3.3 Värikomponenttien muokkaus Seuraavaksi toteutetaan kolme suodinta, joiden toiminta on periaatteiltaan keskenään hyvin samankaltainen. Luomme metodit, jotka muokkaavat kuvan väriä komponentti kerrallaan. Toteutettavat metodit ovat: def adjustred(amount: Int, img: Image) : Unit def adjustgreen(amount: Int, img: Image) : Unit def adjustblue(amount: Int, img: Image) : Unit Kukin metodi muuttaa nimensä mukaisesti yhtä värikomponenttia parametrin amount mukaan kun muut komponentit pysyvät samoina. Metodille välitetty amount-parametrin arvo vaihtelee välillä [- 100, 100]. Tämä arvo voidaan suoraan lisätä komponentin arvoon. Älä muuta alkuperäisen kuvan dataa (sen ei pitäisi edes olla mahdollista) vaan kutsu Image luokan setimage metodia asettaaksesi kuvalle tieto muokatusta kuvadatasta.
4. Filter-luokan monimutkaisemmat suotimet Seuraavassa vaiheessa tulet: Toteuttamaan metodin, jonka avulla voidaan sumentaa kuvaa. Toteuttamaan metodin, jonka avulla voidaan tarkentaa kuvaa. Toteuttamaan näiden metodien hyödyntämät apumetodit. Seuraavaksi toteutettavat suotimet eroavat edellisen osatehtävän suotimista siinä, että pikselien uusi arvo ei muodostu pelkästään alkuperäisen arvon ja jonkin operaation pohjalta, vaan uuteen arvoon vaikuttaa pikselin naapurusto. Sekä blur että sharpen suotimet kertovat kunkin pikselin arvon, mutta kertoimena käytetään yksittäisen arvon sijaan taulukkoa. Suodintaulukot ovat kaksiulotteisia taulukoita, joilla pituus ja leveys ovat yhtä suuret ja parittomat (esim. 3x3 tai 7x7). Taulukon alkiot ovat Float-tyypin liukulukuja. blur-metodin sumentava toiminnallisuus saadaan keskiarvoistamalla pikselien arvoja. Jokaisen pikselin arvo muodostuu siis sen ympäristön keskiarvosta. sharpen-metodin tarkentava toiminnallisuus taas korostaa kunkin pikselin alkuperäistä väriä suhteessa naapurustoonsa. Kummallekin metodille parametrina välitetään kokonaisluku amount, joka kertoo, kuinka suuri alue pikselin ympäriltä tulee ottaa huomioon. Parittomille luvuille luotavassa suodintaulukossa jokainen alkio on nollasta poikkeava (neliskulmainen suodin) ja parillisille luvuille suodintaulukossa taulukon kulmat jätetään nollaksi, jolloin saadaan neljäkkään muotoisia suotimia. Alla esimerkki. amount = 2 0 x 0 x x x 0 x 0 amount = 3 x x x x x x x x x amount = 4 0 0 x 0 0 0 x x x 0 0 x x x 0 0 0 x 0 0 amount = 5
4.1 Apumetodi getfilter Äsken esitettyjen suotimien aikaansaamiseksi toteuta metodi: private def getfilter(amount: Int, seed: Float) : Array[Array[Float]] Ensimmäinen parametri amount määrittää suotimen muodon ja koon, ja toinen parametri seed on alkioiden arvo. Metodi palauttaa kaksiulotteisen taulukon, jota käytetään suotimena. 4.2 Apumetodi multiplywithfilter Toteuta metodi: private def multiplywithfilter(x: Int, y: Int, image: Image, filter: Array[Array[Float]]) : Color joka kertoo kuvan (image) yksittäisen pikselin (x, y) parametrina välitetyllä suotimella (filter). Metodi palauttaa koordinaateissa (x, y) olevan pikselin uuden arvon. Parametrina annettu pikseli kerrotaan suotimen keskikohdalla. Käy läpi parametrina annetun pikselin (x, y) ympäristö kertoen arvot suotimessa olevilla kertoimilla ja laske laske uusi arvo näiden summasta. Kun suodin on kooltaan 3*3, osuu pikseli (x, y) suotimen kohtaan (1, 1), tällöin pikselin (x, y) uusi arvo muodostuu kun kerrotaan pikseli kohdassa (x-1, y-1) suotimen kohdalla (0,0), pikseli kohdassa (x-1, y) suotimen kohdalla (0, 1), pikseli kohdassa (x-1, y+1) suotimen kohdalla (0, 2) jne. ja lasketaan näiden arvojen summa. Käsittele kukin värikomponentti erikseen ja läpinäkyvyyskomponentti Alpha jää jälleen koskemattomaksi. Kuvan reunoilla suodin ei enää mahdu kokonaisuudessaan kuvan alueelle (esimerkiksi pikseli (0,0) tulisi kertoa vain yhdellä kulmalla). Yksinkertaisuuden nimissä jätämme reunapikselit kokonaan huomioimatta, ja reunoilla palautetaan pikselin alkuperäinen väriarvo. 4.3 Metodit blur ja sharpen Äsken toteutetut apumetodit yksinkertaistavat merkittävästi blur- ja sharpen metodien toteutusta. Kummassakin luodaan suodin getfilter metodin avulla ja kerrotaan sillä kuva multiplywithfilter metodin avulla. Lopuksi kuvaluokalle Image välitetään tieto päivitetystä kuvadatasta Image luokan setimage metodin avulla. Toteuta metodit def blur(amount: Int, image: Image) : Unit ja def sharpen(amount: Int, image: Image) : Unit 4.3.1 blur Sumentaminen on käytännössä keskiarvottamista, eli jokaisella naapuruston pikselillä on samanlainen painoarvo. Jotta kuvan valoisuus ei muuttuisi, on pidettävä huolta siitä, että suodintaulukon alkioiden summa ei ylitä yhtä.
Laske blur metodissa ennen getfilter apumetodin kutsua millä parametrin seed arvolla alkioiden summa on 1. Parittomilla parametrin amount arvoilla (jolloin suodin on neliön mallinen), parametrin seed arvoksi tulee parametrin amount neliön käänteisluku. Parillisilla parametrin amount arvoilla (jolloin suodin on neljäkkään mallinen), parametrin seed arvon voi laskea amount/2 ja amount/2 + 1 neliöiden summan käänteislukuna. Katso vinkiksi alla oleva taulukko. amount = 6 0 0 0 1 0 0 0 0 0 1 1 1 0 0 0 1 1 1 1 1 0 1 1 1 1 1 1 1 0 1 1 1 1 1 0 0 0 1 1 1 0 0 0 0 0 1 0 0 0 Neljäkkään muotoisten suotimien voi ajatella koostuvan kahdesta limittäisestä neliöstä, joista toinen olisi yllä olevassa taulukossa sinisien ja toinen mustien ykkösten määrittelemä. 4.3.2 sharpen Samoin kuin sumentavissa suotimissa myös tarkentavien suodintaulukoiden alkioiden summan tulisi olla yksi. Koska tarkentava suodin korostaa pikselin alkuperäistä arvoa, voidaan laatia getfilter metodilla tarvittava suodin, käyttäen parametrin seed arvona lukua -1. Tämän jälkeen tulee suotimen keskimmäinen arvo korvata sellaisella positiivisella kokonaisluvulla, että suotimen alkioiden arvoksi tulee 1. Alla esimerkki muutamasta suotimesta. amount = 3-1 -1-1 -1 9-1 -1-1 -1 amount = 4 0 0-1 0 0 0-1 -1-1 0-1 -1 13-1 -1 0-1 -1-1 0 0 0-1 0 0 Bonustehtävä: Kuvien julkaisu verkossa Tässä vaiheessa tulet: Rekisteröimään ohjelmasi imgur-kuvapalveluun. Toteuttamaan metodin jonka julkaisee muokatun kuva internetissä ja palauttaa siihen johtavan linkin.
Bonustehtävänä voit luoda imageposter-luokan ainoa metodi, jonka avulla julkaisemme muokatun kuvan imgur-kuvapalveluun HTTP-Post requestia hyödyntäen (https://en.wikipedia.org/wiki/post_(http)). Tulet siis toteuttamaan metodin: def postimage(img: Image): String joka ottaa parametrikseen Image-luokan ilmentymän ja palauttaa merkkijonona kuvaan johtavan URL-osoitteen. Metodia kutsutaan käyttöliittymässä "Post image"-napilla, jota voit käyttää testaukseen. 5.1 Valmistelu Jotta voit hyödyntää imgurin rajapintaa (https://en.wikipedia.org/wiki/application_programming_interface) tulee sinun rekisteröityä imgurin käyttäjäksi saadaksesi oman ohjelmasi yksilöivän CLIENT_ID-tunnuksen. HUOM: jätä oma CLIENT_IDsi pois lopullisesta palautuksesta. CLIENT_ID lähetetään jokaisen palveluun lähtevän HTTP-pyynnön (request) mukana osana sen header-kenttää. Luo siis tunnukset osoitteessa https://www.imgur.com. Tutustu imgurin rajapinnan dokumentaatioon (https://api.imgur.com) ja rekisteröi oma ohjelmasi ohjeiden mukaan. Authorization type-kohdassa valitse "Anonymous usage without user authorization" (lisäämämme kuvat eivät liity mihinkään käyttäjätiliin vaan ne julkaistaan anonyymisti). 5.3 Kuvan lataaminen palveluun HTTP-pyynnön lähettämiseksi käytämme alussa osaksi ohjelmaamme lisäämää scalaj-http-kirjastoa, joka helpottaa pyyntöjen ja vastausten käsittelyä. Tutustu kirjaston dokumentaatioon https://github.com/scalaj/scalaj-http. Vinkki: postdata-metodi voi osoittautua hyödylliseksi. Tutustu myös imgurin dokumentaatioon https://api.imgur.com/endpoints/image ja erityisesti Image Upload-kohtaan. Imgurin rajapinta ei suoraan hyväksy BufferedImage-tyyppistä dataa. Muuta siis lähetettävä kuvadata palvelun tukemaan muotoon ennen sen lähettämistä. Vinkki: esimerkiksi Array[Byte] Kirjoita nyt http-pyyntö, jolla lähetät kuvadatan palveluun. Muista liittää mukaan ohjelmasi CLIENT_ID Imgurin vaatimalla tavalla. Pyynnön palautusarvoksi tulee imgurin palvelimen antama vastaus. Tämän tulostamalla voit testata metodin toimivuutta ja saada vinkkejä mahdollisiin ongelmiin. 5.4 Kuvalinkin palauttaminen käyttäjälle Onnistunut http-pyyntö palauttaa paljon tietoja JSON-muodossa https://en.wikipedia.org/wiki/json, mutta me haluamme välittää ohjelmamme käyttöliittymälle vain kuvaan johtavan linkin. JSON:in käsittelyyn voimme käyttää projektiin lisäämäämme spraykirjastoa. Tutustu kirjaston dokumentaatioon https://github.com/spray/spray-json. Tehtävänäsi on hakea http-pyyntösi vastauksesta kuvan linkki ja palauttaa se metodin palautusarvona. Oikein toteutettuna linkki välittyy nyt ohjelman käyttöliittymälle. Tarkasta linkin toimivuus.
Palautus Testaa vielä lopuksi kaikkien tekemiesi luokkien toiminta ja varmista, että kaikki toimii toivomallasi tavalla. Viimeistään tässä vaiheessa kommentoi koodisi ne pätkät, jotka saattavat olla vaikeaselkoisia. Pakkaa projektisi totuttuun tapaan.zip-paketiksi ja anna sille nimeksi opiskelijanumerosi_image.zip. Kuvatiedostoja ei tarvitse palauttaa. Lisää mukaan halutessasi readme.txt tiedosto jossa voit antaa palautetta tehtävästä ja pohtia mikä oli helppoa tai erityisen haastava. Palauta projekti mycoursesiin.