Movie Mood & Tone with Palette ลองใช้จานสีสร้างดูเล่นๆ
บล็อกก่อนมีคนถามว่าเรื่อง Palette โอเค งั้นเรามา re-write ของเดิมให้จบดีกว่า
เท่าที่เราดูภาพเล่นๆ น่าจะไม่เหมือนที่ Palette generate สีหรือเปล่านะ 555 แอปตัวอย่างในบล็อกนี้ คือ มีรูปจากหนัง แล้วให้มัน generate สีจาก Palette มาให้ ดูสิว่าจะตรง mood & tone หรือไม่
มาทำความรู้จักเจ้า Palette กันก๊อนนนน
palette เป็นหนึ่งใน library ของทางฝั่ง Android เอง โดยการเพิ่มเจ้านี่ลงไปใน dependency
implementation 'com.android.support:palette-v7:28.0.0'
Palette | Android Developers
Synchronous Palette p = Palette.from(bitmap).generate(); // Asynchronous Palette.from(bitmap).generate(new…developer.android.com
ซึ่งไม่น่ามีใครเอ๊ะใจอะไร เนื่องจากตัวอย่างเราใช้เจ้า Android Support 28.0.0 ดังนั้นเทียบเท่ากับ AndroidX 1.0.0 นั่นเอง
implementation 'androidx.palette:palette:1.0.0'
ส่วน Document ที่เกี่ยวกับเจ้า Palette นั้น ก็น่าจะตัวนี้แหละ เพราะใน AndroidX ไม่ได้เขียนไว้
หลักการทำงานคร่าวๆ คือ เอารูปมารูปนึง ที่เป็น bitmap มาทำการ generate โดยเราสามารถดึง color profile ที่ Palette generate มาได้ ดังนี้
- Vibrant :
getVibrantColor()
- Vibrant Dark :
getDarkVibrantColor()
- Vibrant Light :
getLightVibrantColor()
- Muted :
getVibrantColor()
- Muted Dark :
getDarkVibrantColor()
- Muted Light :
getLightVibrantColor()
แต่จริงๆแล้วเจ้า Palette นาง Swatches สีทั้งหมดมาให้ 16 สี โดยเราสามารถนำไปใช้โดยการ getSwatches()
ซึ่งหนุ่มๆไม่ต้องกังวลเวลาสาวขอแขนมา Swatches สีลิปสติก เอ้ยผิดๆๆๆๆๆ ><
การใช้งาน Palette
ก่อนอื่นมาสร้าง instance ของ Palette กันก่อน ซึ่งรองรับการทำงานทั้ง synchronous และ asynchronous
// Generate palette synchronously and return it
fun createPaletteSync(bitmap: Bitmap): Palette = Palette.from(bitmap).generate()
// Generate palette asynchronously and use it on a different
// thread using onGenerated()
fun createPaletteAsync(bitmap: Bitmap) {
Palette.from(bitmap).generate { palette ->
// Use generated instance
}
}
คำถามจากทางบ้านเมื่อคราวนั้น รวมไปถึงพี่เอกก็ทักตอนตรวจดราฟ ว่าทำไมไม่ใช้ palette generate สีให้เลยหล่ะ ตอนนี้เริ่มได้คำตอบนึงแล้วว่า มันรับ input เป็น bitmap มา generate สีให้เรา แล้วคำถามต่อไปว่า จะทำยังไงให้เป็น bitmap หล่ะ ในกรณีของ
- ถ้าใส่เป็นสีหล่ะ ทำงานได้ไหม?
ก่อนอื่นต้องแปลงเป็น bitmap เสียก่อน แล้วจะแปลงยังไง google it แล้วพบอันนี้
แล้วใส่ไปแบบนี้
val bitmap = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
canvas.drawColor(ContextCompat.getColor(this, R.color.colorBg))
จากนั้นเอาเข้าไปใน Palette ซึ่งเอาตัวอย่างจากใน Document มา
Palette.from(bitmap).generate { palette ->
textview.setTextColor(palette?.vibrantSwatch!!.titleTextColor)
}
ผลที่ได้คือ อื้มมม แบบนี้นี่เองง แต่ไม่ตรงทุกอันแหะ อันนี้ต้องไป work ต่อเอาเองว่าอันไหนจะได้สีตัวหนังสือที่เราต้องการจริงๆ แต่จริงๆสีตัวหนังสือมันไม่ออกขาวดำเลย มันเป็นสีๆไง
อธิบายเพิ่มเติม แต่ละ swatch นั้น สามารถดึง rgb, hsl, population รวมไปถึง title และ body text color ได้ด้วย มิน่าาา ทำไมถึงทักกันว่าทำไมไม่ใช่ Palette คือตอนแรกก็นึกว่าทำได้เฉพาะรูปอย่างเดียวซะอีก ฮ่าาาๆๆๆ
- แล้ว drawable ที่เป็นรูปต้องแปลงเป็น bitmap ก่อน?
คราวนี้เราจะลองให้แสดงรูปต้นแบบด้านบน และสีที่ได้ด้านล่างเนอะ พร้อมชื่อของมันไปเลยจ้า การทำ layout ในตอนนี้ขอถึกนิดนึง คือสร้าง TextView มา 7 ตัว พร้อมใส่แต่ละ swatch จ้า
- เจ้า drawable นั้น สามารถ get แบบปกติโดยใช้
ContextCompat
และแปลงเป็นBitmapDrawable
โดย getbitmap
มาอีกที
(ContextCompat.getDrawable(this, R.drawable.sample) as BitmapDrawable).bitmap
- แต่ละ swatch ที่ได้นั้น เช่น
palette?.darkMutedSwatch
output ที่ได้คือPalette.Swatch
ดังนั้นเราจะไปใช้ตรงๆในการ set color ต่างๆไม่ได้นะจ๊ะ
- การนำสีแต่ละ swatch ไปใช้นั้น จะต้องห้อยด้วย .rgb เสมอ เช่น
palette.darkMutedSwatch!!.rgb
- แน่นอนว่าพวก text color จะมี 2 ตัวให้ใช้ คือ titleTextColor (
palette?.darkMutedSwatch!!.titleTextColor
) กับ bodyTextColor (palette?.darkMutedSwatch!!.bodyTextColor
) ห้อยท้ายนั่นเอง
ดังนั้นผลที่ได้จะเป็นแบบนี้
- แล้วถ้ารูปมาจาก url เราต้องให้ method ของ library นั้นๆทำให้เป็น bitmap
เราใช้ Library Fresco ในการดึงภาพจาก url ซึ่งจริงๆแล้วใช้ตั้งแต่ตัวอย่างที่แล้วแล้วแหละ 55555
ความเดิมตอนที่แล้ว
แต่ความยากเพิ่มขึ้นคือ ต้องทำให้เป็น Bitmap กับรูปตัวอย่างของเรา ซึ่งมันแอบไม่สอดคล้องกับความตั้งใจของเจ้า Fresco เท่าไหร่นัก 555
เท่าที่ดูมี 2 ways คือ ใช้ pipeline ใน Fresco กับ ดึงจาก connection ยัดเข้า InputStream แล้วได้ Bitmap มา ซึ่งซับซ้อนน้อยกว่าวิธีแรก งั้นลอง way ที่สองเนอะ
ข้อควรระวัง ใส่ async ด้วยไม่งั้นมันจะ crash นะเออ ซึ่งใส่ async ได้สองแบบ
แบบแรกแบบ สร้าง class AsyncTask
การเรียกใช้จะเป็นแบบนี้
AsyncGettingBitmapFromUrl().execute(url).get()
แบบที่สอง ใช้ Coroutines
ก่อนอื่นเข้าไปเพิ่มใน dependency
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.1'
จากนั้นสร้าง function get bitmap จาก image url มา ซึ่งเหมือนข้างบน
private fun getBitmapFromUrl(src: String): Bitmap {
val url = URL(src)
val connection = url.openConnection() as HttpURLConnection
connection.doInput = true
connection.connect()
val input = connection.inputStream
return BitmapFactory.decodeStream(input)
}
วิธีการใช้งาน
GlobalScope.async {
createPaletteAsync(getBitmapFromUrl(url))
}
ซึ่งจะเขียนเรื่อง coroutine ทีหลังเนอะ :D
แต่เราก็หนี crash ไม่พ้น พอลอง debug เหมือนบางสีมันไม่มีให้ง่ะ ดังนั้นอย่าลืมดักด้วย
เพราะรูปตัวอย่างมันได้สีมาแค่นี้ใช่ไหม แต่ 16 สีมันดันมาครบไง
งั้นลองเอา Mood & Tone ที่เราเห็นๆตามเว็บมาลองดูสักหน่อย
สุดท้ายกับการโชว์ swatch ทั้ง 16 สี ก็ดูจะค่อนข้างตรงบ้างเนอะ ในบางสี 555
ส่วนอันนี้สีเยอะเฉ้ยยย เท่าที่ดูเนี่ย สีแต่ละ swatch ก็อยู่ใน swatch ที่เป็น list อีกที
การประยุกต์ใช้ สามารถเอาสีใน Palette ไปใส่ตอน loading รูป เห็นหลายๆแอปชอบทำกัน เช่น Pinterest
ที่ทำไปก็ไม่แน่ใจว่า เขาหาสี Mood & Tone ของหนังแต่ละเรื่องยังไง เพราะของเขามีกัน 10 สี ของเราเนี่ยมีสีอะไรบ้าง 7 + 16 สีอ่ะ งั้นลองอ่านอันนี้ดู มีหลายองค์ประกอบเลยนะเนี่ย
ส่วนอันนี้ละเอียดจนล้องห้ายยย มันยาวมาก
Reference รูปเผื่ออยากเอาไปเล่นกัน :
วันดีคืนดีอาจจะมีคนแชร์โพสประมาณนี้ใน Facebook :)
โค้ดทั้งหมดอยู่ในนี้ มาส่องกันได้จ้า
ถ้ามีเวลาว่างๆจะตัดซีนหนังจากลิ้งด้านบนมาให้ swatch สีนะ น่าสนุกดี :D
สุดท้ายฝากร้านกันสักนิด ฝากเพจด้วยนะจ๊ะ