ARMアセンブリ言語でRGBA->HSVに変換してみた件

# きっかけ

写真の色分析をするのに、RGBA形式の画像をHSV形式に変換しています。
いつもはOpenCV(c++)を使っているのですが、どうもその処理が遅いようなので、ARMアセンブリ言語の勉強を兼ねて作ってみました。
（というか、ネット上に無かったので自分で作らざるを得なかった）

前回、[c++とアセンブリ言語で画像を反転させてみる](http://qiita.com/wf9a5m75/items/a519d0f82fe1484179c5)をやったので
できるだろ、とか思ったら、甘かった。。。。


# RGBA画像をHSVに変換するコード(c++)

[Googleで検索](https://www.google.com/search?q=c%2B%2B+rgb+to+hsv&oq=c%2B%2B+rgb+to+hsv)するといくつかでてきますが、
今回はそのなかで[Convert RGB to HSV and back with a micro controller](https://www.ruinelli.ch/rgb-to-hsv)のコードを参考にしました。

理由は unsigned char型を使っていたので、アセンブリ言語に落とし込みやすかったから。

コードはこんな感じです。

最後の部分の **0.71**って数字は、Hの値の範囲を0〜360から、0〜256に変換しているだけです。
(0.71 ≒ 256/360)

```cpp
typedef struct {
    unsigned char *data;
    int width;
    int height;
    int bpp;
    int size;
} image_t;


void rgba2hsvx_c(image_t *rgba, image_t *hsv) {
  unsigned char *src = (unsigned char *)rgba->data;
  unsigned char *dst = (unsigned char *)hsv->data;

  unsigned char min, max, delta;
  unsigned char r,g,b,a;
  unsigned char s,v;
  signed int h;

  int x, y;

  for (y = 0; y < rgba->height; y++) {
    for (x = 0; x < rgba->width; x++) {
      r = *src++;
      g = *src++;
      b = *src++;
      a = *src++;

      min = r < g ? r : g;
      min = b < min ? b : min;

      max = r > g ? r : g;
      max = b > max ? b : max;

      v = max;
      delta = max - min;

      if (!(max == 0 || delta == 0)) {

        s = (int)(delta * 255) / max;

        if (r == max) {
          h = (60 * (g - b))  / delta;
        } else {
          if (g == max) {
            h = 120 + ((60 * (b - r)) / delta);
          } else {
            h = 240 + ((60 * (r - g)) / delta);
          }
        }

        if (h < 0)  {
          h += 360;
        }
      } else {
        h = 0;
        s = 0;
      }

      *dst++ = (unsigned int)(h * 0.71);   // In real value range is 0-359, but for rgba image, down scale to 0-255
      *dst++ = s;
      *dst++ = v;
      *dst++ = a;
    }
  }
}
```

# アセンブリコード

めちゃくちゃ苦労しました。5日間、脳みそ使用率120%って感じｗ
（ARMアセンブリコードを書き始めてから、まだ2週間ですので）

```cpp
void rgba2hsvx_neon(image_t *rgba, image_t *hsv) {
  unsigned char *src = (unsigned char *)rgba->data;
  unsigned char *dst = (unsigned char *)hsv->data;

  __asm__ __volatile__(

    "            vmov.i32       d0, #0\n"                    // d0 (s0, s1) = 0x0000000000000000
    
    "            lsr            %[total], %[total], #5\n"    // %[size] = %[size] / 32 ; The number '32' means 4bytes * 8pixels
    
    "            mov            r9, %[src]\n"
    "            mov            r10, %[dst]\n"
    
    "            mov            r0, #60\n"
    "            vmov.u32       s31, r0\n"                   // (d15) s31 = 60
    "            vcvt.f32.u32   s31, s31\n"
    
    "            mov            r0, #255\n"
    "            vmov.i32       s30, r0\n"                   // (d15) s30 = 255
    "            vcvt.f32.u32   s30, s30\n"
    
    "            mov            r0, #256\n"
    "            vmov.s32       s29, r0\n"                   // (d14) s29 = 256
    "            vcvt.f32.u32   s29, s29\n"
    
    "            mov            r0, #360\n"
    "            vmov.u32       s28, r0\n"                   // (d14) s28 = 360
    "            vcvt.f32.u32   s28, s28\n"
    
    "            mov            r0, #120\n"
    "            vmov.s32       s27, r0\n"                   // (d13) s27 = 120
    "            vcvt.f32.u32   s27, s27\n"
    
    "            mov            r0, #240\n"
    "            vmov.u32       s26, r0\n"                   // (d13) s26 = 240
    "            vcvt.f32.u32   s26, s26\n"
    
    "loop_hsv:\n"
    "            pld            [r9, #256]\n"
    "            vld4.8         {d1, d2, d3, d4}, [r9]\n"    // Load elements from %[src] and store to each D registers:
                                                             //  [RED]   d1 = [r7, r6, ... r0]
                                                             //  [GREEN] d2 = [g7, g6, ... g0]
                                                             //  [BLUE]  d3 = [b7, b6, ... b0]
                                                             //  [ALPHA] d4 = [a7, a6, ... a0]
    
    "            vmin.u8        d5, d1, d2\n"                // min = MIN(r, g);
    "            vmin.u8        d5, d5, d3\n"                // min = MIN(b, min);
    
    "            vmax.u8        d6, d1, d2\n"                // max = MAX(r, g);
    "            vmax.u8        d6, d6, d3\n"                // max = MAX(b, max);
    
    "            vqsub.u8       d7, d6, d5\n"                // delta = max - min
    
                                                             //if (!(max == 0 || delta == 0))
    "            vceq.u8        d4, d7, d0\n"                // d4 = max == 0 ? 0xFF : 0
    "            vceq.u8        d8, d6, d0\n"                // d8 = delta == 0 ? 0xFF : 0
    "            vorr.u8        d8, d4, d8\n"                // d8 = d4(0 or 0xFF) | d8(0 or 0xFF)
    
    
    "            vst4.8         {d5, d6, d7, d8}, [r10]\n"   // (min) : (max) : (delta) : (flag)
    
    "            mov            r0, #0\n"                    // H = 0
    "            mov            r1, #0\n"                    // S = 0
    "            mov            r2, #0\n"                    // V = 0
    "            mov            r8, #8\n"                    // r8 (i) = 8
    
    //------------
    //   if (!(max == 0 || delta == 0)) {
    //------------
    "calc_hsv:\n"
    "            pld            [r10, #32]\n"                // Preload 32bit data from the main memory
    "            ldrb           r3, [r10]\n"                 // flag = *r10++
    
    "            cmp            r3, #0xFF\n"                 // if (flag == 0xff)
    "            beq            calc_hsv_end\n"              //    goto calc_s
    //------------
    //       s = (int)(delta * 255) / max;
    //------------
    "calc_s:\n"
    
    "            pld            [r9, #32]\n"                // Preload 256bit data from the main memory
    "            ldrb           r5, [r9]\n"                  // red(r5) = *r9
    "            ldrb           r6, [r9, #1]\n"              // green(r6) = *(r9 + 1)
    "            ldrb           r3, [r9, #2]\n"              // blue(r3) = *(r9 + 2)
    
    "            vmov.u32       s2, r5\n"                    // s2 = red
    "            vmov.u32       s3, r6\n"                    // s3 = green
    "            vmov.u32       s4, r3\n"                    // s4 = blue
    "            vcvt.f32.u32   s2, s2\n"                    // convert unsigned int to floating point
    "            vcvt.f32.u32   s3, s3\n"
    "            vcvt.f32.u32   s4, s4\n"
    
    "            ldrb           r2, [r10, #1]\n"             // max(V) = *(r10 + 1)
    "            ldrb           r3, [r10, #2]\n"             // delta = *(r10 + 2)
    "            vmov.u32       s5, r3\n"                    // s5 = delta
    "            vmov.u32       s6, r2\n"                    // s6 = max
    "            vcvt.f32.u32   s5, s5\n"                    // unsigned int 32 bits -> float 32 bits
    "            vcvt.f32.u32   s6, s6\n"
    
    "            vmul.f32       s7, s5, s30\n"               // s7 = s5(delta) * s30(#255)
    "            vdiv.f32       s7, s7, s6\n"                // s7 = s7 / s6(max)
    "            vcvt.u32.f32   s7, s7\n"                    // float 32 bits -> unsigned int 32 bits
    "            vmov           r1, s7\n"                    // r1(S) = s7
    
    
    //---------------
    //    s6(offset) = 0
    //    if (r == max) {
    //      s7 = (g-b)
    //    } else {
    //      if (g == max) {
    //        s7 = (b - r)
    //        s6 = 120
    //      } else {
    //        s7 = (r - g)
    //        s6 = 240
    //      }
    //   }
    //   s7 = s7 * 60
    //   s7 = s7 / delta
    //   s7 += offset
    //   H = s7 < 0 ? s7 + 360 : s7
    //---------------
    "            cmp            r2, r5\n"                    // if ( r2(max) == r5(red) )
    "            beq            r_equal_max\n"               //   goto r_equal_max
    "            cmp            r2, r6\n"                    // if ( r2(max) == r6(green) )
    "            beq            g_equal_max\n"               //   goto g_equal_max
    
    
    "b_equal_max:\n"
    "            vsub.f32       s7, s2, s3\n"                // s7 = s2(red) - s3(green)
    "            vmov           s6, s26\n"                   // s6 = s26(#240)
    "            bal            calc_h\n"                    // goto calc_h
    
    "g_equal_max:\n"
    "            vsub.f32       s7, s4, s2\n"                // s7 = s4(blue) - s2(red)
    "            vmov           s6, s27\n"                   // s6 = s27(#120)
    "            bal            calc_h\n"                    // goto calc_h
    
    "r_equal_max:\n"
    "            vmov           s6, s0\n"                    // s6 = s0(#0)
    "            vsub.f32       s7, s3, s4\n"                // s7 = s3(green) - s4(blue)
    
    
    "calc_h:\n"
    "            vmul.f32       s7, s7, s31\n"               // s7 = s7 * s31(#60)
    "            vdiv.f32       s7, s7, s5\n"                // s7 = s7 / s5(delta)
    "            vadd.f32       s7, s7, s6\n"                // s7 += s6(offset)
    
    "            vaddlt.f32     s7, s28\n"                   // s7 = s7 < 0 ? s7 + s28(#360) : 0
    "            vmul.f32       s7, s7, s29\n"               // s7 = s7 * s29(#256)
    "            vdiv.f32       s7, s7, s28\n"               // s7 = s7 / s28(#360)
    "            vcvt.u32.f32   s7, s7\n"                    // float 32bit -> unsigned int 32bit
    "            vmov           r0, s7\n"                    // r0(H) = s7
    
    "calc_hsv_end:\n"
    "            mov            r3, #0xFF\n"
    "            strb           r0, [r10]\n"                 // H
    "            strb           r1, [r10, #1]\n"             // S
    "            strb           r2, [r10, #2]\n"             // V
    "            strb           r3, [r10, #3]\n"             // A = 0xFF
    "            add            r9, r9, #4\n"
    "            add            r10, r10, #4\n"
    
    
    "            subs           r8, r8, #1\n"                // r8--
    "            bne            calc_hsv\n"                  // if (r8 != 0) goto calc_hsv
    "            subs           %[total], %[total], #1\n"    // %[total]--
    "            bne            loop_hsv\n"                  // if (%[total] != 0) goto loop_hsv
    


    :
    : [src]"r"(src) , [dst]"r"(dst), [total]"r"(rgba->size)
    : "q0", "q1", "q2", "q3", "r0",
      "r1", "r2", "r3", "r5", "r6", "r4", "r8", "r9", "r10",
      "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", "s26", "s27",
      "s28", "s29", "s30", "s31", "memory", "cc"
  );

}
```

# 結果どうなった

1920x1080の画像をRK3288 CPUで試してみました。
たぶん使用しているメモリとかの関係だと思いますが、他のデバイスでテストすると結果が変わります。

方法 | 時間
--- | ---
OpenCV(3.1) | 約84ms
c++ (-O3) | 約193ms
assembly (neon) | 約80ms

うーん、苦労した割には大差がない。

# バグ？

どうゆうわけか、OpenCVでHSVに変換したときと出力される画像が微妙に違うんですよね。
でもc++とは同じになるので、**（とりあえず）良し**、とはしていますが。

元画像はこれ。使用しているデバイスの中にサンプル画像としてあったものを1920x1080にリサイズして使用。

<a href="https://qiita-image-store.s3.amazonaws.com/0/39529/9b1e9199-65a0-0b5e-9759-b87b6167bca8.jpeg"><img src="https://qiita-image-store.s3.amazonaws.com/0/39529/9b1e9199-65a0-0b5e-9759-b87b6167bca8.jpeg" width="400"></a>

※ _(画像をクリックすればダウンロードできます)_

OpenCVで変換すると、こうなります。なんかシュール...
<a href="https://qiita-image-store.s3.amazonaws.com/0/39529/89ea8c90-4ad4-7c08-df1a-8d2bc9d51dde.png"><img src="https://qiita-image-store.s3.amazonaws.com/0/39529/89ea8c90-4ad4-7c08-df1a-8d2bc9d51dde.png" width="400"></a>

アセンブリコードで変換するとこうなります。
<a href="https://qiita-image-store.s3.amazonaws.com/0/39529/0ce21037-c00c-c4fc-cffd-2c6ca0a7ee27.png"><img src="https://qiita-image-store.s3.amazonaws.com/0/39529/0ce21037-c00c-c4fc-cffd-2c6ca0a7ee27.png" width="400"></a>

どうも鳥の胸のあたりが結果が違うんですよね。
理由はいまのところ不明です。

##VFPレジスタ、Dレジスタ、Qレジスタ

やっている途中でハマったのが、レジスタのマップ。
`s0 = 360`とコードの前方で定義して、後半になるとぜんぜん違う値が入っていて、最初は計算が全然合わない謎の現象が発生して、随分悩まされました。

調べたところ、VFPレジスタはD/Qレジスタと同じ空間なのだと。
なぬぅ！！

そんなことは、[AndroidNDKネイティブプログラミング第2版](http://amzn.to/2iCXjXf)には書いてなかった！

[公式サイトの解説](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0204gj/Chdidbba.html)によると、次のように書いてあります。

>VFP コプロセッサには、s0 〜 s31 の 32 本の単精度レジスタがあります。各レジスタは、単精度浮動小数点値または 32 ビット整数を保持できます。
これら 32 本のレジスタは、d0 〜 d15 の 16 本の倍精度レジスタとしても使用されます。dn は、s(2n) および s(2n+1) と同じハードウェアを使用します。

図解すると、こうらしい。

![registers.png](https://qiita-image-store.s3.amazonaws.com/0/39529/55cf56e7-6bec-31d3-a1b4-5aca040bf741.png)


つまり、**d0レジスタに値を保存してあるのに、s3レジスタに値をうっかり保存すると、d0レジスタは影響を受ける**のです。

これを念頭にいれて、コードを読んで下さい。

##コードの解説

将来の自分のために、残しておきます。
まぁコメントを読めばわかる気がするけど。

### 定数の宣言

コード中で計算のための定数をsレジスタに定義しておきます。

うっかり上書きしないようにするために、定数をsレジスタの後ろの方に入れておきます。

![step1.png](https://qiita-image-store.s3.amazonaws.com/0/39529/e65431d8-faf8-83b0-9def-aced894179b9.png)


### min, max, deltaの計算

NEON命令を使って、8ピクセル分をまとめて計算してしまいます。

![step2.png](https://qiita-image-store.s3.amazonaws.com/0/39529/09bfcc37-a371-f760-201e-a612997477fa.png)

### max == 0 && delta == 0 の判定

2つの値を比較するif文は面倒だったので、NEON命令を使って、8ピクセル分をまとめて判定しておきます。
![step3.png](https://qiita-image-store.s3.amazonaws.com/0/39529/2d3848aa-3bc2-afb0-41d9-1fd4282d91c6.png)


### 計算結果を一時保存

そしてこれらの値を 書き出し領域の`%[dst] (r10)`に一時保存します。
（※メインメモリを使わなかったら、もっと早くなるかも...）

この段階で、8ピクセル分の min, max(V), delta, flagが計算できました。

![step4.png](https://qiita-image-store.s3.amazonaws.com/0/39529/98568a60-c637-6c82-d9c1-f8129f15d907.png)

### Sの計算

書き出し領域に保存した1ピクセル分のデータを読み出して、1ピクセルずつ処理していきます。
(* ホントはこれも並列化したい)

HSVのSの計算はこの部分です。
c++に書いてある計算をVFPを使って計算しているだけです。

```cpp
//------------
//       s = (int)(delta * 255) / max;
//------------
"calc_s:\n"

"            pld            [r9, #256]\n"                // Preload 256bit data from the main memory
"            ldrb           r5, [r9]\n"                  // red(r5) = *r9
"            ldrb           r6, [r9, #1]\n"              // green(r6) = *(r9 + 1)
"            ldrb           r7, [r9, #2]\n"              // blue(r7) = *(r9 + 2)

"            vmov.u32       s2, r5\n"                    // s2 = red
"            vmov.u32       s3, r6\n"                    // s3 = green
"            vmov.u32       s4, r7\n"                    // s4 = blue
"            vcvt.f32.u32   s2, s2\n"                    // convert unsigned int to floating point
"            vcvt.f32.u32   s3, s3\n"
"            vcvt.f32.u32   s4, s4\n"

"            ldrb           r2, [r10, #1]\n"             // max(V) = *r11++
"            ldrb           r3, [r10, #2]\n"             // delta = *r11++
"            vmov.u32       s5, r3\n"                    // s5 = delta
"            vmov.u32       s6, r2\n"                    // s6 = max
"            vcvt.f32.u32   s5, s5\n"                    // unsigned int 32 bits -> float 32 bits
"            vcvt.f32.u32   s6, s6\n"

"            vmul.f32       s7, s5, s30\n"               // s7 = s5(delta) * s30(#255)
"            vdiv.f32       s7, s7, s6\n"                // s7 = s7 / s6(max)
"            vcvt.u32.f32   s7, s7\n"                    // float 32 bits -> unsigned int 32 bits
"            vmov           r1, s7\n"                    // r1(S) = s7
```


### Hの計算

HSVのHの計算はこの部分です。
(* ホントはこれも並列化したい)

```cpp
//---------------
//    s6(offset) = 0
//    if (r == max) {
//      s7 = (g-b)
//    } else {
//      if (g == max) {
//        s7 = (b - r)
//        s6 = 120
//      } else {
//        s7 = (r - g)
//        s6 = 240
//      }
//   }
//   s7 = s7 * 60
//   s7 = s7 / delta
//   s7 += offset
//   H = s7 < 0 ? s7 + 360 : s7
//---------------
"            cmp            r2, r5\n"                    // if ( r2(max) == r5(red) )
"            beq            r_equal_max\n"               //   goto r_equal_max
"            cmp            r2, r6\n"                    // if ( r2(max) == r6(green) )
"            beq            g_equal_max\n"               //   goto g_equal_max


"b_equal_max:\n"
"            vsub.f32       s7, s2, s3\n"                // s7 = s2(red) - s3(green)
"            vmov           s6, s26\n"                   // s6 = s26(#240)
"            bal            calc_h\n"                    // goto calc_h

"g_equal_max:\n"
"            vsub.f32       s7, s4, s2\n"                // s7 = s4(blue) - s2(red)
"            vmov           s6, s27\n"                   // s6 = s27(#120)
"            bal            calc_h\n"                    // goto calc_h

"r_equal_max:\n"
"            vmov           s6, s0\n"                    // s6 = s0(#0)
"            vsub.f32       s7, s3, s4\n"                // s7 = s3(green) - s4(blue)


"calc_h:\n"
"            vmul.f32       s7, s7, s31\n"               // s7 = s7 * s31(#60)
"            vdiv.f32       s7, s7, s5\n"                // s7 = s7 / s5(delta)
"            vadd.f32       s7, s7, s6\n"                // s7 += s6(offset)

"            vaddlt.f32     s7, s28\n"                   // s7 = s7 < 0 ? s7 + s28(#360) : 0
"            vmul.f32       s7, s7, s29\n"               // s7 = s7 * s29(#256)
"            vdiv.f32       s7, s7, s28\n"               // s7 = s7 / s28(#360)
"            vcvt.u32.f32   s7, s7\n"                    // float 32bit -> unsigned int 32bit
"            vmov           r0, s7\n"                    // r0(H) = s7
```

### 最後に保存

あとは書き出し領域に保存して、次の8ピクセルの処理に移ります。

```cpp
"calc_hsv_end:\n"
"            mov            r3, #0xFF\n"
"            strb           r0, [r10]\n"                 // H
"            strb           r1, [r10, #1]\n"             // S
"            strb           r2, [r10, #2]\n"             // V
"            strb           r3, [r10, #3]\n"             // A = 0xFF
"            add            r9, r9, #4\n"
"            add            r10, r10, #4\n"


"            subs           r8, r8, #1\n"                // r8--
"            bne            calc_hsv\n"                  // if (r8 != 0) goto calc_hsv
"            subs           %[total], %[total], #1\n"                // sub--
"            bne            loop_hsv\n"                  // if (r11 != 0) goto calc_hsv
```

# まとめ

記事を書くのに疲れたので、一旦まとめます。

このコードを書くのに、5日間フルで使いましたね。
朝も昼も夜も、寝ている間でさえもうなされながら、考えてました。

で、**結果が、OpenCVと対して変わらない！！！！**  ... orz

でもSの計算を並列化できれば、たぶん早くなるはず。と思っているので、誰かヘルプしてください

# 追記1. Sの計算は並列化できない（たぶん）

[ARM32ビットCPUの場合、除算を擬似的に行っている](http://atkg.hatenablog.com/entry/2016/11/26/205019)らしく、[計算に32ビット幅を要し](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0204ij/CIHCDBIG.html)ます。

そして一回の命令で一つの値しか計算できないので、除算の部分はどうしても並列化はできないっぽい。

# 追記2. 3週間バトルしてみてその後...

最終的にはこんなコードにしました。でもOpenCVの方がちょっとだけ早い。。。のでギブアップ

```cpp
void rgba2hsvx_neon(image_t *rgba, image_t *hsv) {
  volatile unsigned char *src = (unsigned char *)rgba->data;
  volatile unsigned char *dst = (unsigned char *)hsv->data;

  __asm__ __volatile__(
    "           mov             r0, %[src]\n"
    "           pld             [r0]\n"

    "           mov             r1, %[dst]\n"
    "           mov             r7, %[size]\n"
    "           lsr             r7, r7, #5\n"           // size = size >> 5

    "           vmov.u32        d25, #255\n"             //d25 = 255.0f
    "           vcvt.f32.u32    d25, d25\n"

    "           vmov.u32        q15, #0xff\n"            //q15(d30, d31) = 0xFF
    "           vmov.u32        d29, #120\n"             //d29 = 120
    "           vmov.u32        d28, #240\n"             //d28 = 240

    "           vmov.u32        d27, #60\n"              //d27 = 60.0f

    "           vadd.u32        d15, d29, d28\n"         //d15 = 360.0f
    "           vcvt.f32.u32    d15, d15\n"

    "           vmov.u32        d14, #256\n"             //d14 = 0.71f
    "           vcvt.f32.u32    d14, d14\n"
    "           vdiv.f32        s28, s28, s30\n"
    "           vmov            s29, s28\n"

    "           vmov.u8         d23, #0xFF\n"


    "loop_hsv:\n"
    "           vld4.8          {d0-d3}, [r0]\n"         // r,g,b,a

    //-----------------
    // d4 = max
    // d5 = delta
    //-----------------
    "           vmin.u8         d5, d0, d1\n"            // min = MIN(r, g);
    "           vmin.u8         d5, d5, d2\n"            // min = MIN(b, min);

    "           vmax.u8         d22, d0, d1\n"           // max = MAX(r, g);
    "           vmax.u8         d22, d22, d2\n"          // max = MAX(b, max);
    "           vqsub.u8        d5, d22, d5\n"           // delta = max - min


    //--------------------------------
    // zero clear of the store space
    //--------------------------------
    "           mov             r4, #4\n"
    "           vmov.u64        q10, #0\n"               // For the final result of H and S values
    "           vmov            d4, d22\n"

    "calc_s:\n"
    //------------------------------------------------
    // Shift the store space for H and S (d20, d21)
    //------------------------------------------------
    "           vshl.u32        q10, #8\n"               // q10(d20,d21) = q10 << 8

    //----------------------------------------
    // Load values from d3(max), d4(delta)
    //----------------------------------------
    "           vand.u32        q4, q2, q15\n"           // d8(max) = d4(max) & d30(#0xff), d9(delta) = d5(delta) & d31(#0xff)

    //----------------
    // calculate H
    //----------------
    "           vsub.u64        d11, d11, d11\n"         // d11 = 0
    "           vmov.u32        d18, d31\n"              // d18 = d31(0xFF)

    //--------------------------------
    // if (red == max) {
    //   op1 = green
    //   op2 = blue
    // }
    //--------------------------------
    "           vand.u32        d17, d31, d0\n"          // d17 = red & 0xff
    "           vceq.u32        d7, d17, d8\n"           // d7 = red == max ? 0xFF : 0
    "           vsub.u32        d18, d18, d7\n"          // d18 = d18 - d7
    "           vand.u32        d7, d7, d31\n"           // d7 = d7 & 0xff
    "           vand.u32        d10, d7, d1\n"           // d10(op1) = d7 & green
    "           vand.u32        d12, d7, d2\n"           // d12(op2) = d7 & blue


    //--------------------------------
    // if (green == max) {
    //   op1 = blue
    //   op2 = red
    //   offset = 120
    // }
    //--------------------------------
    "           vand.u32        d17, d31, d1\n"          // d17 = green & 0xff
    "           vceq.u32        d7, d17, d8\n"           // d7 = green == max ? 0xFF : 0
    "           vsub.u32        d18, d18, d7\n"          // d18 = d18 - d7
    "           vand.u32        d7, d7, d31\n"           // d7 = d7 & 0xff
    "           vand.u32        d7, d7, d18\n"           // d7 = d7 & d18
    "           vand.u32        d17, d7, d2\n"           // d17 = d7 & blue
    "           vorr.u32        d10, d10, d17\n"         // d10(op1) = d10 | d17
    "           vand.u32        d17, d7, d0\n"           // d17 = d7 & red
    "           vorr.u32        d12, d12, d17\n"         // d12(op2) = d12 | d17
    "           vand.u32        d11, d7, d29\n"          // d11(offset) = d7 & d29(#120)

    //--------------------------------
    // if (blue == max) {
    //   op1 = red
    //   op2 = green
    //   offset = 240
    // }
    //--------------------------------
    "           vand.u32        d17, d31, d2\n"          // d17 = blue & 0xff
    "           vceq.u32        d7, d17, d8\n"           // d7 = blue == max ? 0xFF : 0
    "           vand.u32        d7, d7, d31\n"           // d7 = d7 & 0xff
    "           vand.u32        d7, d7, d18\n"           // d7 = d7 & d18
    "           vand.u32        d17, d7, d0\n"           // d17 = d7 & red
    "           vorr.u32        d10, d10, d17\n"         // d10(op1) = d10 | d19
    "           vand.u32        d17, d7, d1\n"           // d17 = d7 & green
    "           vorr.u32        d12, d12, d17\n"         // d12(op2) = d12 | d17
    "           vand.u32        d17, d7, d28\n"          // d17 = d7 & d28(#240)
    "           vorr.u32        d11, d11, d17\n"         // d11(offset) = d11 | d17


    //--------------------------------
    // h = ((op1 - op2) * 60) / delta
    //--------------------------------
    "           vand.u32        d10, d10, d31\n"         // d10(op1) = d10 & d31(#0xFF)
    "           vand.u32        d12, d12, d31\n"         // d12(op2) = d12 & d31(#0xFF)
    "           vsub.s32        d10, d10, d12\n"         // d10 = d10(op1) - d11(op2)
    "           vmul.s32        d10, d10, d27\n"         // d10 = d10 * d27(#60)
    "           vcvt.f32.u32    q5, q5\n"                // unsigned int -> float
    "           vcvt.f32.u32    q4, q4\n"                // (d8, d9, d10, d11)
    "           vdiv.f32        s20, s20, s18\n"         // s20 = s20 / s18(delta)
    "           vdiv.f32        s21, s21, s19\n"         // s21 = s21 / s19(delta)
    "           vadd.f32        d10, d10, d11\n"         // d10 += d11(offset)

    //-------------------------
    // h += h < 0 ? 360.0f : 0
    //-------------------------
    "           vclt.f32        d11, d10, #0\n"          // d11 = d10 < 0 ? 0xFF : 0
    "           vand.f32        d11, d15\n"              // d11 = d11 & d15(#360.0f)
    "           vadd.f32        d10, d11\n"              // d10 = d10 + d11
    //---------------------------------------
    // downscale H value from 0-359 to 0-255
    //---------------------------------------
    "           vmul.f32        d10, d10, d14\n"         // d10 = d10 * 0.71

    //------------------------
    // s = delta * 255 / max
    //------------------------
    "           vmul.f32        d11, d9, d25\n"          // d11 = d9 * d25(#255.0f)
    "           vdiv.f32        s22, s22, s16\n"         // s22 = s22 / s16(max)
    "           vdiv.f32        s23, s23, s17\n"         // s23 = s23 / s17(max)

    "           vcvt.u32.f32    q5, q5\n"                // float -> unsigned int (d10, d11)

    "           vorr.u64        q10, q5, q10\n"          // H = H | d10, S = S | d11

    "           vshr.u64        q0, q0, #8\n"            // q0(red, green) = q0 >> 8
    "           vshr.u64        d2, d2, #8\n"            // d2(blue) = q1 >> 8
    "           vshr.u64        q2, q2, #8\n"            // q2(delta, max) = q2 >> 8

    "           subs            r4, r4, #1\n"
    "           bne             calc_s\n"

    "           pldw            [r1]\n"
    "           vrev32.8        q10, q10\n"
    "           vst4.8          {q10-q11}, [r1]\n"       // r,g,b,a


    "           add             r0, r0, #32\n"
    "           add             r1, r1, #32\n"
    "           pld             [r0]\n"

    "           subs            r7, r7, #1\n"
    "           bne             loop_hsv\n"



    :
    : [src]"r"(src), [dst]"r"(dst), [size]"r"(rgba->size)
    : "memory", "cc", "r0",  "r1", "r4", "r5", "r6", "r7"
  );

}
```