すべてのプロダクト
Search
ドキュメントセンター

Object Storage Service:Android での HEIC および AVIF デコード

最終更新日:Apr 02, 2025

このトピックでは、Android クライアントで HEIC 画像と AVIF 画像をデコードするための依存関係を構成する方法について説明します。

Android での HEIC デコード

HEIC は、JPEG 形式よりも最新の圧縮アルゴリズムを使用する画像形式です。HEIC 画像は、同等の品質の JPEG 画像の約 40% のサイズです。

Alibaba Cloud のパフォーマンス専有型 HEIC デコードライブラリは、オープンソースライブラリ libheif および libde265 に基づいて開発され、ARM 向けに最適化されています。このエンドツーエンドのデコード最適化は、他の類似のオープンソースライブラリの強みをベースにしており、デコード速度を大幅に向上させます。Alibaba Cloud HEIC デコードライブラリは、元のオープンソースライブラリよりもはるかに効率的です。Alibaba Cloud のパフォーマンス専有型 HEIC デコードライブラリのサンプルコードについては、GitHub をご覧ください。

Android での HEIC デコードの構成

Android で HEIC デコードを構成するには、次の手順を実行します。

  1. Alibaba Cloud Maven リポジトリ構成を build.gradle の repositories ノードに追加します。

    maven { url 'https://maven.aliyun.com/repository/public' }
  2. 次の依存関係を build.gradle ファイルの dependencies ノードに追加します。

    implementation 'com.aliyun:libheif:1.0.0'

難読化の構成

難読化を構成する場合、HEIC デコードが期待どおりに動作するように、次の文をコードに追加します。

-keep class com.aliyun.libheif.**

HEIC 画像のデコード

  • シナリオ 1:API 操作を呼び出して HEIC 画像をデコードする

        /**
         * HEIC ファイルを RGBA 形式にデコードする
         * @param length  有効なファイルメモリのサイズ
         * @param filebuf メモリ内のファイルポインタ
         * @return rgba バイト。{@link android.graphics.Bitmap} の作成に便利
         */
        public static native boolean toRgba(long length, byte[] fileBuf, Bitmap bitmap);
    
        /**
         * ファイルが heic 形式かどうかを判断する
         * @param length  有効なファイルメモリのサイズ
         * @param filebuf メモリ内のファイルポインタ
         * @return bool。true の場合、形式は heif です。
         *               false の場合、形式は heif ではありません。
         */
        public static native boolean isHeic(long length, byte[] fileBuf);
    
        /**
         * heic 画像から情報を取得する
         * @param HeifInfo 情報
         * @param length  有効なファイルメモリのサイズ
         * @param filebuf メモリ内のファイルポインタ
         * @return bool。true の場合、outSize は有効です。
         *               false の場合、outSize は無効です。
         */
        public static native boolean getInfo(HeifInfo info, long length, byte[] fileBuf);                

    次のサンプルコードは、asset ディレクトリにある test.heic 画像を bitmap 画像としてデコードする方法の例を示しています。

    val image = findViewById<ImageView>(R.id.image);
    val inputStream  = assets.open("test.heic")
    val buffer = ByteArray(8192)
    var bytesRead: Int
    val output = ByteArrayOutputStream()
    while (inputStream.read(buffer).also { bytesRead = it } != -1) {
                output.write(buffer, 0, bytesRead)
            }
    
    val heifInfo = HeifInfo()
    val fileBuffer: ByteArray = output.toByteArray()
    
    HeifNative.getInfo(heifInfo, fileBuffer.size.toLong(), fileBuffer)
    // frameList パラメータから画像の高さと幅(最初の要素)を取得します。画像は単一フレーム画像です。
    val heifSize = heifInfo.frameList.first()
    // 画像の高さと幅に基づいて画像を作成します。
    val bitmap = Bitmap.createBitmap(
                heifSize.width,
                heifSize.height,
                Bitmap.Config.ARGB_8888)
    // 画像をデコードします。
    val heifBuffer = HeifNative.toRgba(fileBuffer.size.toLong(), fileBuffer, bitmap)
    image.setImageBitmap(bitmap)
  • シナリオ 2:デコードライブラリを Glide と統合し、ライブラリを使用して HEIC 画像をデコードする

    Glide は、一般的なオープンソースの画像キャッシュライブラリです。詳細については、GitHub の Glide をご参照ください。

    1. build.gradle ファイルを変更して、デコードライブラリを Glide と統合します。

      implementation 'com.aliyun:libheif:1.0.0'
      implementation 'com.github.bumptech.glide:glide:4.13.2'
      implementation 'com.github.bumptech.glide:compiler:4.13.2'
    2. decode メソッドで Alibaba Cloud のパフォーマンス専有型 HEIC デコードライブラリを指定します。詳細については、Glide のカスタムモジュール をご参照ください。

      class HeifByteBufferBitmapDecoder(bitmapPool: BitmapPool): ResourceDecoder<ByteBuffer, Bitmap> {
      
          private val bitmapPool: BitmapPool
      
          init {
              this.bitmapPool = Preconditions.checkNotNull(bitmapPool)
          }
      
          override fun handles(source: ByteBuffer, options: Options): Boolean {
              val buffer = ByteBufferUtil.toBytes(source)
              return HeifNative.isHeic(buffer.size.toLong(), buffer);
          }
      
          override fun decode(
              source: ByteBuffer,
              width: Int,
              height: Int,
              options: Options
          ): Resource<Bitmap>?  {
              val buffer = ByteBufferUtil.toBytes(source)
              var heifInfo = HeifInfo()
              HeifNative.getInfo(heifInfo, buffer.size.toLong(), buffer)
              val heifSize = heifInfo.frameList[0]
              val bitmap = Bitmap.createBitmap(heifSize.width, heifSize.height, Bitmap.Config.ARGB_8888)
              HeifNative.toRgba(buffer.size.toLong(), buffer, bitmap)
              return BitmapResource.obtain(bitmap, bitmapPool)
          }
      }
    3. デコーダーモジュールを登録します。

      @GlideModule(glideName = "HeifGlide")
      open class HeifGlideModule : LibraryGlideModule() {
          override fun registerComponents(
              context: Context, glide: Glide, registry: Registry
          ) {
              val byteBufferBitmapDecoder = HeifByteBufferBitmapDecoder(glide.bitmapPool)
              registry.prepend(
                  ByteBuffer::class.java,
                  Bitmap::class.java, byteBufferBitmapDecoder
              )
          }
      }
    4. HEIF 画像を読み込んで表示します。

      fun loadHeif(context: Context, imageView: ImageView, file: File) {
           Glide.with(context).asBitmap().load(file).into(imageView)
       }
  • シナリオ 3:デコードライブラリを Fresco と統合し、ライブラリを使用して HEIF 画像をデコードする

    Fresco は、Facebook によって開発されたオープンソースの Android 画像キャッシュライブラリです。詳細については、GitHub の Fresco をご参照ください。

    1. build.gradle ファイルを変更して、デコードライブラリを Fresco と統合します。

      implementation 'com.aliyun:libheif:1.0.0'
      implementation 'com.facebook.fresco:fresco:2.6.0'
    2. decode メソッドで Alibaba Cloud のパフォーマンス専有型 HEIC デコードライブラリを指定します。

      class FrescoHeifDecoder: ImageDecoder {
      
          private var mFile: File? = null
          private var mUri: Uri? = null
      
          constructor(file: File) {
              mFile = file
          }
      
          constructor(uri: Uri) {
              mUri = uri
          }
      
          override fun decode(
              encodedImage: EncodedImage,
              length: Int,
              qualityInfo: QualityInfo,
              options: ImageDecodeOptions
          ): CloseableImage? {
              var imageRequest: ImageRequest? = null
              if (mFile != null) {
                  imageRequest = ImageRequest.fromFile(mFile)
              }
              if (mUri != null) {
                  imageRequest = ImageRequest.fromUri(mUri)
              }
              try {
                  val cacheKey = DefaultCacheKeyFactory.getInstance()
                      .getEncodedCacheKey(imageRequest, null)
                  val fileCache = ImagePipelineFactory.getInstance()
                      .mainFileCache
                  val resource = fileCache.getResource(cacheKey)
                  val file: File = if (resource == null) {
                      mFile!!
                  } else {
                      (resource as FileBinaryResource).file
                  }
      
                  val bufferFromFile = ByteBufferUtil.fromFile(file)
                  val bytes = ByteBufferUtil.toBytes(bufferFromFile)
                  val heifInfo = HeifInfo()
                  HeifNative.getInfo(heifInfo, file.length(), bytes)
                  val heifSize = heifInfo.frameList[0]
                  val bitmap =
                      Bitmap.createBitmap(heifSize.width, heifSize.height, Bitmap.Config.ARGB_8888)
      
                  HeifNative.toRgba(file.length(), bytes, bitmap)
      
                  return CloseableStaticBitmap(
                      pinBitmap(bitmap),
                      qualityInfo,
                      encodedImage.rotationAngle,
                      encodedImage.exifOrientation
                  )
              } catch (e: Exception) {
              }
              return null
          }
      
          private fun pinBitmap(bitmap: Bitmap?): CloseableReference<Bitmap>? {
              return CloseableReference.of(
                  Preconditions.checkNotNull(bitmap),
                  BitmapCounterProvider.get().releaser
              )
          }
      }

      Alibaba Cloud のパフォーマンス専有型 HEIC デコードライブラリに接続する場合は、ByteBufferUtil リファレンスクラスを呼び出します。

      object ByteBufferUtil {
      
          @Throws(IOException::class)
          fun fromFile(file: File): ByteBuffer {
              var raf: RandomAccessFile? = null
              var channel: FileChannel? = null
              return try {
                  val fileLength = file.length()
                  if (fileLength > Int.MAX_VALUE) {
                      throw IOException("File too large to map into memory")
                  }
                  if (fileLength == 0L) {
                      throw IOException("File unsuitable for memory mapping")
                  }
                  raf = RandomAccessFile(file, "r")
                  channel = raf.channel
                  channel.map(FileChannel.MapMode.READ_ONLY, 0, fileLength).load()
              } finally {
                  if (channel != null) {
                      try {
                          channel.close()
                      } catch (e: IOException) {
                      }
                  }
                  if (raf != null) {
                      try {
                          raf.close()
                      } catch (e: IOException) {
                      }
                  }
              }
          }
      
          fun toBytes(byteBuffer: ByteBuffer): ByteArray {
              val result: ByteArray
              val safeArray = getSafeArray(byteBuffer)
              if (safeArray != null && safeArray.offset == 0 && safeArray.limit == safeArray.data.size) {
                  result = byteBuffer.array()
              } else {
                  val toCopy = byteBuffer.asReadOnlyBuffer()
                  result = ByteArray(toCopy.limit())
                  rewind(toCopy)
                  toCopy[result]
              }
              return result
          }
      
          private fun rewind(buffer: ByteBuffer): ByteBuffer {
              return buffer.position(0) as ByteBuffer
          }
      
          private fun getSafeArray(byteBuffer: ByteBuffer): SafeArray? {
              return if (!byteBuffer.isReadOnly && byteBuffer.hasArray()) {
                  SafeArray(byteBuffer.array(), byteBuffer.arrayOffset(), byteBuffer.limit())
              } else null
          }
      
          internal class SafeArray
              (val data: ByteArray, val offset: Int, val limit: Int)
      }
    3. HEIF 画像を読み込みます。

      fun loadHeif(simpleDraweeView: SimpleDraweeView, file: File) {
              val request =
                  ImageRequestBuilder.newBuilderWithSource(Uri.fromFile(file)).setImageDecodeOptions(
                      ImageDecodeOptions.newBuilder().setCustomImageDecoder(FrescoHeifDecoder(file))
                          .build()
                  ).build()
              val controller = Fresco.newDraweeControllerBuilder()
                  .setImageRequest(request).build()
      
              simpleDraweeView.controller = controller
          }

Android での AVIF デコード

AVIF は、AV1 ビデオエンコーディングに基づく新しい画像形式であり、JPEG や WebP よりも高い圧縮率と画像の詳細を提供します。AVIF 画像は、同等の品質の JPEG 画像の約 35% のサイズです。

libavif ライブラリを使用して、AVIF 画像をデコードできます。libavif は、関連する Android API 操作をカプセル化します。関連コードは android_jni ディレクトリに保存されています。詳細については、GitHub をご覧ください。

デコードライブラリを AVIF と統合する

build.gradle に次の依存関係を追加します。

implementation'org.aomedia.avif.android:avif:0.11.1.3c786d2'

直接デコードを使用する

次のサンプルコードは、decode メソッドを直接使用して ByteBuffer 内の AVIF ファイルをデコードする方法の例を示しています。

/**
   * AVIF 画像をビットマップにデコードします。
   *
   * @param encoded エンコードされた AVIF 画像。encoded.position() は 0 である必要があります。
   * @param length エンコードされたバッファのサイズ。
   * @param bitmap デコードされたピクセルはビットマップにコピーされます。
   * @param threads AVIF デコードに使用するスレッド数。ゼロは、CPU コア数を使用することを意味します
   *     スレッド数として。負の値は無効です。この値が > 0 の場合、単純に
   *     libavif の maxThreads パラメータにマッピングされます。詳細については、
   *     avif.h の maxThreads 変数のドキュメントを参照してください。
   * @return 成功時は true、失敗時は false。失敗の考えられる理由は次のとおりです。1) 入力
   *     有効な AVIF ではなかった。2) ビットマップはデコードされた画像を保存するのに十分な大きさではなかった。3) 負
   *     スレッドパラメータに値が渡されました。
   */
  public static native boolean decode(ByteBuffer encoded, int length, Bitmap bitmap, int threads);

threads パラメータを 1 に設定することをお勧めします。値 1 は、シングルスレッドデコードを使用することを指定します。画像エンコーディングメソッドで AV1 タイル機能を使用している場合は、マルチスレッディングを有効にして効率を向上させることができます。詳細については、GitHub をご覧ください。

デコードライブラリを Glide と統合する

Glide のカスタムモジュールは、AVIF デコードをサポートしています。ソースコードについては、GitHub のコードリポジトリをご参照ください。

Glide 4.13.0 以降では、次の依存関係を追加して、Glide を使用して AVIF ファイルをデコードできます。

説明

Glide 4.13.0 以降を使用していることを確認してください。

implementation "com.github.bumptech.glide:avif-integration:4.13.2"       

デコードライブラリを Fresco と統合する

デコードライブラリを Fresco と統合する方法の詳細については、「画像形式のカスタマイズ」をご参照ください。