- apng라는 파일 형식이 있다. PNG 포맷을 기반으로 한 애니메이션 이미지 포맷으로서 GIF와 비슷하지만 더 높은 품질과 투명도를 지원하는 특징이 있다.
- 기존에 png, jpg, gif를 활용했었으나 용량 관리 측면에서 apng가 유리하여 새롭게 사용하게 되었다.
- APNG 파일은 PNG와 동일한 구조를 가지므로, APNG를 지원하지 않는 프로그램에서도 정적인 PNG 이미지로 보일 수 있음 (하위 호환성)
- 그래서 apng가 움직이지 않고 정적인 이미지로 보인다면, 사실 완벽한 렌더링에는 실패했거나 해당 기반 프로그램이 지원하지 않는 것
- 토스와 라인도 apng를 많이 사용하고, 라인에서 안드로이드에서 해당 형식 렌더링을 위한 라이브러리도 구현해 주어서 직접 사용해 봤다.
https://github.com/line/apng-drawable
GitHub - line/apng-drawable: A lightweight and fast Animated Portable Network Graphics (APNG) image decoder for Android
A lightweight and fast Animated Portable Network Graphics (APNG) image decoder for Android - line/apng-drawable
github.com
- 먼저 app 수준의 gradle에 등록해 준다.
implementation 'com.linecorp:apng:1.10.0'
(1.12.0부터는 코틀린 2.0이 아니면 빌드가 안되니 참고.)
- 일반 PNG와 APNG를 구분하는 확장 함수를 구성했다. 이를 통한 라이브러리 사용 여부 판단이 필요했다.
fun File.isApng(): Boolean {
if (!this.exists()) return false
val pngSignature = byteArrayOf(
0x89.toByte(), 0x50.toByte(), 0x4E.toByte(), 0x47.toByte(),
0x0D.toByte(), 0x0A.toByte(), 0x1A.toByte(), 0x0A.toByte()
)
FileInputStream(this).use { inputStream ->
val header = ByteArray(8)
if (inputStream.read(header) != 8 || !header.contentEquals(pngSignature)) {
return false // PNG 형식이 아님
}
val buffer = ByteArray(1024)
while (inputStream.read(buffer) != -1) {
val content = String(buffer, Charsets.UTF_8)
if (content.contains("acTL")) {
return true // APNG 청크 발견
}
}
}
return false // acTL 청크가 없으면 일반 PNG
}
- 파일이 PNG인지 확인
- PNG 파일의 **시그니처(첫 8바이트)**를 비교.
- 0x89 50 4E 47 0D 0A 1A 0A는 모든 PNG 파일이 공통으로 가지는 시그니처
- 파일 내부에 acTL 청크(애니메이션 제어 청크) 확인
- APNG는 일반 PNG와 동일한 확장자를 가지지만, 내부에 acTL이라는 특별한 청크가 포함되어 있다.
- 파일 내용을 검색하여 "acTL" 문자열이 포함되어 있는지 검사한다.
- 위 라이브러리가 내부적으로 PNG 데이터를 디코딩하고 프레임을 관리하는 구조로 보인다.
- 이제 라이브러리 사용 방법에 따라 apng 파일을 통해 ApngDrawable을 생성하고, 이미지 뷰에 적용하면 된다.
// Decode from File
val drawable1 = ApngDrawable.decode(File("path/to/file"))
// Decode from InputStream
val drawable2 = File("path/to/file").inputStream().use {
ApngDrawable.decode(it)
}
// Decode from Resources
val drawable3 = ApngDrawable.decode(context.resources, R.raw.apng_image)
...
imageView.setImageDrawable(ApngDrawable.decode(file))
- 위젯의 경우에는 RemoteViews에 Bitmap만 적용이 가능하다. 따라서 Glide를 활용하여 asBitmap() 처리해 준 후, 렌더링 했다.
if (file.isApng()) {
Glide.with(context)
.asBitmap()
.load(ApngDrawable.decode(file))
.into(object : CustomTarget<Bitmap>() {
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
remoteViews.setImageViewBitmap(imageViewId, resource)
}
override fun onLoadCleared(placeholder: Drawable?) {}
})
- apng 테스트 이미지 파일은 아래 링크에서 참고 가능하다.
- apng라는 파일 형식이 있다. PNG 포맷을 기반으로 한 애니메이션 이미지 포맷으로서 GIF와 비슷하지만 더 높은 품질과 투명도를 지원하는 특징이 있다.
- 기존에 png, jpg, gif를 활용했었으나 용량 관리 측면에서 apng가 유리하여 새롭게 사용하게 되었다.
- APNG 파일은 PNG와 동일한 구조를 가지므로, APNG를 지원하지 않는 프로그램에서도 정적인 PNG 이미지로 보일 수 있음 (하위 호환성)
- 그래서 apng가 움직이지 않고 정적인 이미지로 보인다면, 사실 완벽한 렌더링에는 실패했거나 해당 기반 프로그램이 지원하지 않는 것
- 토스와 라인도 apng를 많이 사용하고, 라인에서 안드로이드에서 해당 형식 렌더링을 위한 라이브러리도 구현해 주어서 직접 사용해 봤다.
https://github.com/line/apng-drawable
GitHub - line/apng-drawable: A lightweight and fast Animated Portable Network Graphics (APNG) image decoder for Android
A lightweight and fast Animated Portable Network Graphics (APNG) image decoder for Android - line/apng-drawable
github.com
- 먼저 app 수준의 gradle에 등록해 준다.
implementation 'com.linecorp:apng:1.10.0'
(1.12.0부터는 코틀린 2.0이 아니면 빌드가 안되니 참고.)
- 일반 PNG와 APNG를 구분하는 확장 함수를 구성했다. 이를 통한 라이브러리 사용 여부 판단이 필요했다.
fun File.isApng(): Boolean {
if (!this.exists()) return false
val pngSignature = byteArrayOf(
0x89.toByte(), 0x50.toByte(), 0x4E.toByte(), 0x47.toByte(),
0x0D.toByte(), 0x0A.toByte(), 0x1A.toByte(), 0x0A.toByte()
)
FileInputStream(this).use { inputStream ->
val header = ByteArray(8)
if (inputStream.read(header) != 8 || !header.contentEquals(pngSignature)) {
return false // PNG 형식이 아님
}
val buffer = ByteArray(1024)
while (inputStream.read(buffer) != -1) {
val content = String(buffer, Charsets.UTF_8)
if (content.contains("acTL")) {
return true // APNG 청크 발견
}
}
}
return false // acTL 청크가 없으면 일반 PNG
}
- 파일이 PNG인지 확인
- PNG 파일의 **시그니처(첫 8바이트)**를 비교.
- 0x89 50 4E 47 0D 0A 1A 0A는 모든 PNG 파일이 공통으로 가지는 시그니처
- 파일 내부에 acTL 청크(애니메이션 제어 청크) 확인
- APNG는 일반 PNG와 동일한 확장자를 가지지만, 내부에 acTL이라는 특별한 청크가 포함되어 있다.
- 파일 내용을 검색하여 "acTL" 문자열이 포함되어 있는지 검사한다.
- 위 라이브러리가 내부적으로 PNG 데이터를 디코딩하고 프레임을 관리하는 구조로 보인다.
- 이제 라이브러리 사용 방법에 따라 apng 파일을 통해 ApngDrawable을 생성하고, 이미지 뷰에 적용하면 된다.
// Decode from File
val drawable1 = ApngDrawable.decode(File("path/to/file"))
// Decode from InputStream
val drawable2 = File("path/to/file").inputStream().use {
ApngDrawable.decode(it)
}
// Decode from Resources
val drawable3 = ApngDrawable.decode(context.resources, R.raw.apng_image)
...
imageView.setImageDrawable(ApngDrawable.decode(file))
- 위젯의 경우에는 RemoteViews에 Bitmap만 적용이 가능하다. 따라서 Glide를 활용하여 asBitmap() 처리해 준 후, 렌더링 했다.
if (file.isApng()) {
Glide.with(context)
.asBitmap()
.load(ApngDrawable.decode(file))
.into(object : CustomTarget<Bitmap>() {
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
remoteViews.setImageViewBitmap(imageViewId, resource)
}
override fun onLoadCleared(placeholder: Drawable?) {}
})
- apng 테스트 이미지 파일은 아래 링크에서 참고 가능하다.