Jetpack Compose ile Image

Ömer Durmaz
9 min readDec 25, 2022

--

Merhaba, bu yazımızda Jetpack Compose da Image kullanımından bahsedeceğiz. Ayrıca internetten resmi nasıl yükleyebiliriz, gelen resmi nasıl özelleştirebiliriz ya da performans açısından nelere dikkat etmeliyiz bunlara değineceğiz. O zaman başlayalım;

Resim Gösterme

Diskten bir resim gösterme

Image içinde bir resim gösterirken painter parametresini doldurmamız gerekir. painter sadece Painter tipini kabul eder. Bunun için painterResource apisine diskten göstermek istediğimiz herhangi bir resmin yolunu vererek basitçe ekranda gösterebiliriz.

Image(
painter = painterResource(id = R.drawable.dog),
contentDescription = null
)

Ayrıca contentDescription parametresini de tanımlamamız gerekmektedir. Ancak burada herhangi bir description verme zorunluluğumuz yoktur sadece null değerini vererek de tanımlamayı yapabiliriz.

İnternetten bir resim gösterme

Resmi bir url adresinden çekmek için çok fazla third-party kütüphane bulunuyor. Bu kütüphaneler bir resmi internetten çekip kullanmak dışında resmi ön bellekte tutarak ekran her güncellendiğinde tekrar tekrar resmi internetten çekmeye çalışmaz ve internet kullanımını önemli ölçüde azaltmış olurlar. Burada iki farklı kütüphaneyi önerebilirim, biri Coil diğeri ise Glide. Aşağıda gösterdiğim örnekte Coil kütüphanesi kullanılmıştır.

AsyncImage(
model = "https://example.com/image.jpg",
contentDescription = "Translated description of what the image contains"
)

ImageBitmap ve ImageVector Farkları

Resimlerin nasıl kullanılacağından bahsettiysek biraz da Image içinde kullanılan ImageBitmap ve ImageVector farklarından bahsedelim. Şunu biliyoruz bilinen ve en yaygın kullanılan iki görüntü tipinden birisi Bitmap bir diğeri ise Vector dür.

Bitmap formatı aslında bir resmin piksellerini içinde tutar ve her bir piksel kendi içinde kırmızı, yeşil, mavi ve alfa (RGBA) değerlerinden oluşur. Bu piksellerin birleşimi de bize Bitmap ‘ i yani resmi verir. Ancak bu da eğer resim yakınlaştırılırsa ayrıntıların kaybolması ve pikselleşme sorununu yanında getirir. Bitmap grafik tipindeki resim formatlarına JPEG, PNG ve WEBP örnek verilebilir.

Vector resimler ise ekrandaki görsel bir öğenin ölçeklenebilir matematiksel bir temsilidir. Yani vektör piksellerden oluşmaz ekrana çizilir. Bunu kendi içindeki bir dizi komut meydana getirir. Bu da görsel ne kadar yakınlaştırılırsa yakınlaştırılsın görüntünün bozulmaması olanağını sağlar. Çünkü matematiksel formül diğer komutlar arasındaki ilişkiyi korur. Vektörel resimlere en iyi örnek Material semboller verilebilir.

Vector resim örneği

ImageBitmap

Bitmap bir resim Bitmap ‘ de kullanılırken klasik yöntem ile yazılabilir. painterResource içine verilen bir Bitmap kaynağını ImageBitmap içine taşır. Ardından BitmapPainter resmi ekrana çizer.

Image(
painter = painterResource(id = R.drawable.dog),
contentDescription = stringResource(id = R.string.dog_content_description)
)

Farz edelim ki bir Bitmap ‘ i özelleştirmek bazı pikselleriyle oynamak istedik. O zaman da ImageBitmap tipinde bir değişken oluşturup. Üzerinde değişiklikler yapıp doğruca Image içine verebiliriz.

    val imageBitmap = ImageBitmap.imageResource(R.drawable.dog)    

Image(
imageBitmap,
contentDescription = null
)

ImageVector

Vector resimleri ekrana çizmekten VectorPainter sorumludur. ImageVector SVG tipindeki görselleri ekrana çizebilir. Her resim Vector olarak kullanılamaz ya da Vector ‘ e dönüştürülemez, örneğin kameradan çekilen herhangi bir fotoğraf.

Not: Sadece dışarıdan Vector tipinde görsel almamız gerekmez. Kendimiz de .xml bir dosya oluşturup vektörel bir görsel çizebilir ve bunu Image içinde kullanabiliriz.

Vector bir resim Image ‘ de kullanılırken klasik yöntem ile yazılabilir. painterResource içine verilen bir Vector kaynağını ImageVector içine taşır. Ardından VectorPainter resmi ekrana çizer.

Image(
painter = painterResource(id = R.drawable.baseline_shopping_cart_24),
contentDescription = stringResource(id = R.string.dog_content_description)
)

Ayrıca Bitmap gibi Vector ‘ ü de özelleştirmek istersek ImageVector tipinde bir değişken oluşturup gerekli değişiklikleri yapıp doğruca Image içine verebiliriz.

val imageVector = ImageVector.vectorResource(id = R.drawable.baseline_shopping_cart_24)

Image(
imageVector,
contentDescription = null
)

Resmi Özelleştirme

Bu kısımda ise bir resmin içeriğini ContentScale ile ölçeklendirecek, ColorFilter ile renkleri ve pikselleri ile oynayacak ve Modifier ile de şeklini değiştireceğiz.

ContentScale

ContentScale adından da anlaşılacağı gibi içine tanımlanan görsel içeriğinin ölçeklendirilmesi için kullanılıyor. Öncelikle ContentScale ‘ in kod içinde kullanımını görelim;

Image(
painter= painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = null,
contentScale = ContentScale.Fit
)

Kod içinde kullanımı bu kadar kolay. Peki ContentScale.Fit ne işe yarıyor? Ya da diğer ContentScale tipleri neler hadi bunlara yakından bakalım;

Dikey ve Yatay konumdaki iki kaynak görselimiz.

ContentScale.Fit

Varsayılan ölçeklendirme yöntemi olan ContentScale.Fit resmin en boy oranını koruyarak çizileceği sınırların içine sığdırır.

ContentScale.Fit

ContentScale.Crop

Uygun sınırlar içerisinde içeriği ortadan kırpar.

ContentScale.Crop

ContentScale.FillHeight

En boy oranını koruyarak içeriği sınır yüksekliğe sığdırır.

ContentScale.FillHeight

ContentScale.FillWidth

En boy oranını koruyarak içeriği sınır genişliğe sığdırır.

ContentScale.FillWidth

ContentScale.FillBounds

En boy oranına dikkat etmeden içeriği sınırları dolduracak şekilde ayarlamaya çalışır. Bu genellikle bize sağlıklı bir görünüm vermez.

ContentScale.FillBounds

ContentScale.Inside

Bu tipte iki farklı durumumuz var: Content’in sınır boyutundan büyük olduğu durumlarda en boy oranına dikkat edilerek görsel sınırların içine yerleştirilir. Content in sınır boyutundan küçük olduğu durumlarda ise herhangi bir sığdırma yapılmaz ve sınırların tam merkezine yerleştirilir.

ContentScale.Inside (İçerik sınırdan büyükse)
ContentScale.Inside (İçerik sınırdan küçükse)

ContentScale.None

Herhangi bir ölçeklendirme kullanılmaz. Eğer içerik sınırlardan küçükse merkeze yerleştirilir. Eğer büyükse de içeriğe sığan kısım sınırlar içerisinde gösterilir.

ContentScale.None (İçerik sınırlardan büyükse)
ContentScale.None (İçerik sınırlardan küçükse)

Resmi Kırpma

Bir görseli sınırlar içerisinde nasıl ölçeklendireceğimizi gördük. Hadi şimdi de görsel sınırlarına biraz şekil verelim. Bunun için Modifier.clip kullanmamız gerekmektedir. Eğer resmimize çember şeklini vermek istiyorsanız CircleShape methodunu kullanmak yeterli olacaktır.

Image(
painter = painterResource(id = R.drawable.dog),
contentDescription = stringResource(id = R.string.dog_content_description),
contentScale = ContentScale.Crop,
modifier = Modifier
.size(200.dp)
.clip(CircleShape)
)
CircleShape çıktısı

Eğer resmimizin köşeleri yuvarlatılmış olsun istiyorsak RoundedCornerShape kullanmamız yeterli olacaktır. Methodun içine gireceğimiz Dp değeri ile görselin köşelerine ait keskinliği istediğimiz ölçüde azaltabiliriz.

Image(
painter = painterResource(id = R.drawable.dog),
contentDescription = stringResource(id = R.string.dog_content_description),
contentScale = ContentScale.Crop,
modifier = Modifier
.size(200.dp)
.clip(RoundedCornerShape(16.dp))
)
RoundedCornerShape çıktısı

Ayrıca istersek kendi özel Shape ‘ imizi oluşturabiliriz. Aşağıdaki örnekte SquashedOval şekli Shape ‘ den kalıtım aldığı için görsel özelleştirilmiş olduk.

SquashedOval çıktısı

Resme Çerçeve Ekleme

Genellikle resme çerçeve eklerken yukarıda bahsettiğimiz kırpma işlemiyle birlikte kullanırız. Aşağıdaki örnekte resme çerçeve eklenirken CircleShape olacağı belirtiliyor. Aynı zamanda görsel CircleShape şeklinde kırpılıyor ve bu sayede en düzgün çerçeve eklenmiş oluyor.

val borderWidth = 4.dp
Image(
painter = painterResource(id = R.drawable.dog),
contentDescription = stringResource(id = R.string.dog_content_description),
contentScale = ContentScale.Crop,
modifier = Modifier
.size(150.dp)
.border(
BorderStroke(borderWidth, Color.Yellow),
CircleShape
)
.padding(borderWidth)
.clip(CircleShape)
)
Yukarıdaki kodun çıktısı

Eğer çerçevemizin gradyan olmasını istiyorsak Brush apisini kullanarak bunu yapabiliriz. Tek yapmamız gereken Brush içine gradyanda kullanılacak renkleri vermek.

Yukarıdaki kodun çıktısı

Resme özel en boy oranı belirleme

Herhangi bir resim için en boy oranı belirlemek istediğimizde Modifier.aspectRatio(16f/9f) gibi istediğimiz değerleri verebiliriz.

Image(
painter = painterResource(id = R.drawable.dog),
contentDescription = stringResource(id = R.string.dog_content_description),
modifier = Modifier.aspectRatio(16f / 9f)
)

Resme renk filtresi uygulama

Image ‘ e colorFilter parametresi tanımlayarak resmin tek tek pikselleri ile oynama yapabiliriz.

Resmi renklendirme

ColorFilter.tint(color,blendMode) şeklinde filtre vererek resmi renklendirebiliriz. Varsayılan blendMode değeri BlendMode.SrcIn ‘ dir. Bu resmi renklendirmede en uygun tiptir.

Dipnot: Bu renklendirme işlemi daha çok ikonlarda kullanılır

Image(
painter = painterResource(id = R.drawable.baseline_directions_bus_24),
contentDescription = stringResource(id = R.string.bus_content_description),
colorFilter = ColorFilter.tint(Color.Yellow)
)
ColorFilter.tint

Diğer BlendMode tipleri farklı çıktı sonuçlarına sebep olacaktır. Örneğin BlendMode.Darken ve Color.Green verdiğimizde aşağıdaki çıktıyı elde ederiz;

Image(
painter = painterResource(id = R.drawable.dog),
contentDescription = stringResource(id = R.string.dog_content_description),
colorFilter = ColorFilter.tint(Color.Green, blendMode = BlendMode.Darken)
)
BlendMode.Darken

Diğer BlendMode tipleri için buraya tıklayabilirsin.

Resmi siyah beyaz yapma

ColorMatrix kullanırken eğer saturation değerini 0f yaparsak resmi siyah beyaz olarak ayarlamış oluruz.

Image(
painter = painterResource(id = R.drawable.dog),
contentDescription = stringResource(id = R.string.dog_content_description),
colorFilter = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0f) })
)
ColorMatrix ile siyah beyaz resim

Resmin kontrastını ve parlaklığını değiştirme

val contrast = 2f // 0f..10f (1 değeri varsayılan değerdir)
val brightness = -180f // -255f..255f (0 değeri varsayılan değerdir)
val colorMatrix = floatArrayOf(
contrast, 0f, 0f, 0f, brightness,
0f, contrast, 0f, 0f, brightness,
0f, 0f, contrast, 0f, brightness,
0f, 0f, 0f, 1f, 0f
)
Image(
painter = painterResource(id = R.drawable.dog),
contentDescription = stringResource(id = R.string.dog_content_description),
colorFilter = ColorFilter.colorMatrix(ColorMatrix(colorMatrix))
)
ColorMatrix ile resmin kontrast ve parlaklık değiştirme

Resmin renklerini tersine çevirme

val colorMatrix = floatArrayOf(
-1f, 0f, 0f, 0f, 255f,
0f, -1f, 0f, 0f, 255f,
0f, 0f, -1f, 0f, 255f,
0f, 0f, 0f, 1f, 0f
)
Image(
painter = painterResource(id = R.drawable.dog),
contentDescription = stringResource(id = R.string.dog_content_description),
colorFilter = ColorFilter.colorMatrix(ColorMatrix(colorMatrix))
)
ColorMatrix ile resmin renklerini tersine çevirme

Resmi bulanıklaştırma

Bir resmi bulanıklaştırmak için Modifier.blur() kullanılır. X ve Y yönleri için radiusX ve radiusY değerleri verilmesi gerekir.

Image(
painter = painterResource(id = R.drawable.dog),
contentDescription = stringResource(id = R.string.dog_content_description),
contentScale = ContentScale.Crop,
modifier = Modifier
.size(150.dp)
.blur(
radiusX = 10.dp,
radiusY = 10.dp,
edgeTreatment = BlurredEdgeTreatment(RoundedCornerShape(8.dp))
)
)
Modifier.blur ile resmi bulanıklaştırma

Dipnot: Eğer bulanıklaştıracağımız resme şekil vereceksek aynı şekilde bulanıklaştırmada bu şekli belirtmek, edgeTreatment parametresini BlurredEdgeTreatment.Unbounded yerine BlurredEdgeTreatment(Shape) şeklinde şekil tanımlamak önerilen yöntemdir. Eğer resme şekil vermeyeceksek BlurredEdgeTreatment.Unbounded sorun yaratmayacaktır.

Performans açısından dikkat edilmesi gerekenler

Şimdiye kadar resim ekranda nasıl gösterilir ve nasıl özelleştirilir gibi konulara değindik. Peki bu resmi gösterirken uygulama performansı nasıl etkileniyor? Daha hızlı ve az yer kaplayan resimler kullanmak için neler yapmalıyız yakından bakalım.

Sadece ihtiyacın olan boyutta bitmap göster

Çoğu akıllı telefon kameraları yüksek kalitede resimler çekmemize olanak tanıyor. Eğer bu çekilen görseli ekranda göstermek istersek boyutunu küçültmemiz gerekmektedir. Eğer bunu yapmazsak GPU ‘ nun önbelleğini tüketebilir ve uygulama ekranları açılırken takılarak açılma gibi sorunlarla karşılaşabiliriz. Resim boyutunu küçültmek için:

  • Çıktı görüntüsünü etkilemeyecek kadar resimlerin boyutlarını küçültebiliriz.
  • JPEG ya da PNG kullanmak yerine resimlerimizi WEBP’ ye dönüştürebiliriz.
  • Farklı ekran çözünürlükleri için farklı çözünürlükte görseller sağlayabiliriz.
  • Yukarıda bahsettiğim third-party kütüphaneleri kullanarak fazladan kod yazmadan görsel boyutunu küçülterek ekranda gösterebiliriz.

Yapılabiliyorsa bitmap yerine vector kullan

Ekranda bir resim gösterirken bu resmi acaba vector olarak ekleyebilir miyim diye düşünmek gerekir çünkü bu sayede vector hem daha az yer kaplar, hem de çözünürlük açısından pikselleşme olmaz.

Farklı ekran çözünürlükler için resmin farklı boyutlarını ekle

Farz edelim ki elimizde yüksek çözünürlüklü görsel var, biliyoruz ki bu görseli direkt drawable klasöre ekler ve kullanırsak çözünürlük farketmeksizin bütün cihazlarda bu görsel görüntülenecek. Bu da hem eski cihazlar için render sorununa hem de uygulama boyutunun büyümesine sorun olacaktır. Bunu şu şekilde çözebiliriz; respresso.io gibi kütüphaneler yüksek çözünürlüklü görselleri farklı çözünürlüklere dönüştürmemize yarar. Bu toolu kullanarak elde ettiğimiz yeni çözünürlükteki görselleri aşağıda gösterdiğim şekile projemize ekleyelim. Bu sayede android ekran çözünürlüğüne göre eklediğimiz görsellerden en uygunu seçer ve kullanır.

Dipnot: Bir çok farklı çözünürlükte görsel ekliyoruz diye uygulamamızın boyutu daha fazla büyümez. Uygulamayı store a yüklediğimizde bütün çözünürlükler ile yüklenir ama herhangi bir kullanıcı uygulamayı indirirken store, cihazın çözünürlüğüne bakar ve cihaza en uygun görselle uygulamayı kullandırır. Bu da tam aksine boyut küçülmesine katkı sağlar.

Bu taba tıklayarak eklemek istediğimiz farklı çözünürlükleri seçiyoruz ve yüklüyoruz

ImageBitmap kullanacaksan ekranda göstermeden önce prepareToDraw methodunu çağır

ImageBitmap kullanırken henüz görseli ekranda göstermeden önce ImageBitmap#prepareToDraw() methodunu çağırılırsa GPU ‘ nun görseli hazırlamasına ve ekranda gösterileceği zaman performansın iyileşmesine katkı sağlar. Ama hali hazırda bir third-party bir resim yükleme kütüphanesi varsa muhtemelen o da bu işi kendi içinde yapıyordur.

Composable içine painter atamak yerine url ya da Int DrawableRes ata

Başlık çok açıklayıcı olmasa da şöyle aşağıdaki kod örneğinden ne demek istediğimizi açıklayalım;

// Bunun yerine:
@Composable
fun MyImage(painter: Painter) {

}

// Bunu tercih et:
@Composable
fun MyImage(url: String) {

}

Bunun sebebi Painter api Stable class değildir ve içinde verdiğimiz image — özellikle Bitmap — karmaşık bir yapı olduğu için derleyici stable olmayan classların yani Painter ‘ in değişip değişmediğini kolay kolay anlayamaz. Bundan dolayı da tekrar tekrar görseli ekrana çizmeye çalışır. Eğer bir url ya da id verdiğimizde değişiklik kolaylıkla anlaşılacağı için gereksiz ekran yenilemeleri olmaz.

Çok gerekmedikçe Bitmap’ i parametre içinde tutma

Bitmap bellek açısından çok yer kapladığı eğer çok fazla Bitmap bellekte tutarsak ilerleyen zamanda out of memory hatasıyla karşılaşmamız çok olağan. Örneğin resimlerden oluşan bir liste göstereceğimiz zaman LazyColumn ya da LazyRow kullanmak bizim için daha faydalı olur. Çünkü bu Composable’ lar görsel ekranda olmadığında bellekte tutmaz ve gereksiz yer tutmasını önler.

Büyük boyutlu resimler APK/AAB dosyasında barındırma

En çok karşılaşılan durumlardan biri de büyük boyutlu görsellerin APK/AAB dosyalarda barındırılamasıdır diyebiliriz. Bunun için APK analyzer toolu kullanıp apk mızı analiz edebiliriz. Sadece gerekli resim dosyalarını apk da barındırmak önemlidir.

Bu yazımızda Jetpack Compose Image ile ilgili bir çok konuya değinmeye çalıştık. Okuduğun için teşekkür ederim, bir sonraki yazıda görüşmek üzere.

--

--