| Index: src/opts/Sk4px_NEON.h | 
| diff --git a/src/opts/Sk4px_NEON.h b/src/opts/Sk4px_NEON.h | 
| index cd6dea997945bf7020d5ac08a0ed3b278e1915da..89841d927ee5efd927012ce25f8c110be071f713 100644 | 
| --- a/src/opts/Sk4px_NEON.h | 
| +++ b/src/opts/Sk4px_NEON.h | 
| @@ -89,5 +89,76 @@ inline Sk4px Sk4px::zeroAlphas() const { | 
| return Sk16b(vbicq_u8(this->fVec, (uint8x16_t)vdupq_n_u32(0xFF << SK_A32_SHIFT))); | 
| } | 
|  | 
| +static inline uint8x16_t widen_to_8888(uint16x4_t v) { | 
| +    // RGB565 format:   |R....|G.....|B....| | 
| +    //           Bit:  16    11      5     0 | 
| + | 
| +    // First get each pixel into its own 32-bit lane. | 
| +    //      v ==                       rgb3 rgb2  rgb1 rgb0 | 
| +    // spread == 0000 rgb3  0000 rgb2  0000 rgb1  0000 rgb0 | 
| +    uint32x4_t spread = vmovl_u16(v); | 
| + | 
| +    // Get each color independently, still in 565 precison but down at bit 0. | 
| +    auto r5 = vshrq_n_u32(spread, 11), | 
| +         g6 = vandq_u32(vdupq_n_u32(63), vshrq_n_u32(spread, 5)), | 
| +         b5 = vandq_u32(vdupq_n_u32(31), spread); | 
| + | 
| +    // Scale 565 precision up to 8-bit each, filling low 323 bits with high bits of each component. | 
| +    auto r8 = vorrq_u32(vshlq_n_u32(r5, 3), vshrq_n_u32(r5, 2)), | 
| +         g8 = vorrq_u32(vshlq_n_u32(g6, 2), vshrq_n_u32(g6, 4)), | 
| +         b8 = vorrq_u32(vshlq_n_u32(b5, 3), vshrq_n_u32(b5, 2)); | 
| + | 
| +    // Now put all the 8-bit components into SkPMColor order. | 
| +    return (uint8x16_t)vorrq_u32(vshlq_n_u32(r8, SK_R32_SHIFT),   // TODO: one shift is zero... | 
| +                       vorrq_u32(vshlq_n_u32(g8, SK_G32_SHIFT), | 
| +                       vorrq_u32(vshlq_n_u32(b8, SK_B32_SHIFT), | 
| +                                 vdupq_n_u32(0xFF << SK_A32_SHIFT)))); | 
| +} | 
| + | 
| +static inline uint16x4_t narrow_to_565(uint8x16_t w8x16) { | 
| +    uint32x4_t w = (uint32x4_t)w8x16; | 
| + | 
| +    // Extract out top RGB 565 bits of each pixel, with no rounding. | 
| +    auto r5 = vandq_u32(vdupq_n_u32(31), vshrq_n_u32(w, SK_R32_SHIFT + 3)), | 
| +         g6 = vandq_u32(vdupq_n_u32(63), vshrq_n_u32(w, SK_G32_SHIFT + 2)), | 
| +         b5 = vandq_u32(vdupq_n_u32(31), vshrq_n_u32(w, SK_B32_SHIFT + 3)); | 
| + | 
| +    // Now put the bits in place in the low 16-bits of each 32-bit lane. | 
| +    auto spread = vorrq_u32(vshlq_n_u32(r5, 11), | 
| +                  vorrq_u32(vshlq_n_u32(g6,  5), | 
| +                            b5)); | 
| + | 
| +    // Pack the low 16-bits of our 128-bit register down into a 64-bit register. | 
| +    // spread == 0000 rgb3  0000 rgb2  0000 rgb1  0000 rgb0 | 
| +    //      v ==                       rgb3 rgb2  rgb1 rgb0 | 
| +    auto v = vmovn_u32(spread); | 
| +    return v; | 
| +} | 
| + | 
| + | 
| +inline Sk4px Sk4px::Load4(const SkPMColor16 src[4]) { | 
| +    return Sk16b(widen_to_8888(vld1_u16(src))); | 
| +} | 
| +inline Sk4px Sk4px::Load2(const SkPMColor16 src[2]) { | 
| +    auto src2 = ((uint32_t)src[0]      ) | 
| +              | ((uint32_t)src[1] << 16); | 
| +    return Sk16b(widen_to_8888(vcreate_u16(src2))); | 
| +} | 
| +inline Sk4px Sk4px::Load1(const SkPMColor16 src[1]) { | 
| +    return Sk16b(widen_to_8888(vcreate_u16(src[0]))); | 
| +} | 
| + | 
| +inline void Sk4px::store4(SkPMColor16 dst[4]) const { | 
| +    vst1_u16(dst, narrow_to_565(this->fVec)); | 
| +} | 
| +inline void Sk4px::store2(SkPMColor16 dst[2]) const { | 
| +    auto v = narrow_to_565(this->fVec); | 
| +    dst[0] = vget_lane_u16(v, 0); | 
| +    dst[1] = vget_lane_u16(v, 1); | 
| +} | 
| +inline void Sk4px::store1(SkPMColor16 dst[1]) const { | 
| +    dst[0] = vget_lane_u16(narrow_to_565(this->fVec), 0); | 
| +} | 
| + | 
| } // namespace | 
|  | 
|  |