private void renderBitmapShadow(Canvas targetCanvas, DropShadow shadow) {
    if (targetRect == null || bitmap == null) {
      throw new IllegalStateException("Cannot render to bitmap outside a start()/finish() block");
    }

    // This is an expanded rect that encompasses the full extent of the shadow.
    RectF rectIncludingShadow = calculateRectIncludingShadow(targetRect, shadow);
    if (intRectIncludingShadow == null) intRectIncludingShadow = new Rect();
    intRectIncludingShadow.set(
        (int)Math.floor(rectIncludingShadow.left),
        (int)Math.floor(rectIncludingShadow.top),
        (int)Math.ceil(rectIncludingShadow.right),
        (int)Math.ceil(rectIncludingShadow.bottom)
    );
    float pixelScaleX = preExistingTransform != null ? preExistingTransform[Matrix.MSCALE_X] : 1.0f;
    float pixelScaleY = preExistingTransform != null ? preExistingTransform[Matrix.MSCALE_Y] : 1.0f;
    if (scaledRectIncludingShadow == null) scaledRectIncludingShadow = new RectF();
    scaledRectIncludingShadow.set(
        rectIncludingShadow.left * pixelScaleX,
        rectIncludingShadow.top * pixelScaleY,
        rectIncludingShadow.right * pixelScaleX,
        rectIncludingShadow.bottom * pixelScaleY
    );

    if (shadowBitmapSrcRect == null) shadowBitmapSrcRect = new Rect();
    shadowBitmapSrcRect.set(0, 0, (int)Math.round(scaledRectIncludingShadow.width()), (int)Math.round(scaledRectIncludingShadow.height()));
    if (needNewBitmap(shadowBitmap, scaledRectIncludingShadow)) {
      if (shadowBitmap != null) {
        deallocateBitmap(shadowBitmap);
      }
      if (shadowMaskBitmap != null) {
        deallocateBitmap(shadowMaskBitmap);
      }

      shadowBitmap = allocateBitmap(scaledRectIncludingShadow, Bitmap.Config.ARGB_8888);
      shadowMaskBitmap = allocateBitmap(scaledRectIncludingShadow, Bitmap.Config.ALPHA_8);
      shadowBitmapCanvas = new Canvas(shadowBitmap);
      shadowMaskBitmapCanvas = new Canvas(shadowMaskBitmap);
    } else {
      if (shadowBitmapCanvas == null || shadowMaskBitmapCanvas == null || clearPaint == null) {
        throw new IllegalStateException("If needNewBitmap() returns true, we should have a canvas and bitmap ready");
      }
      shadowBitmapCanvas.drawRect(shadowBitmapSrcRect, clearPaint);
      shadowMaskBitmapCanvas.drawRect(shadowBitmapSrcRect, clearPaint);
    }

    if (shadowMaskBitmap == null) throw new IllegalStateException("Expected to have allocated a shadow mask bitmap");
    
    if (shadowPaint == null) {
      shadowPaint = new LPaint(Paint.ANTI_ALIAS_FLAG);
    }

    // This is the offset of targetRect inside rectIncludingShadow
    float offsetX = targetRect.left - rectIncludingShadow.left;
    float offsetY = targetRect.top - rectIncludingShadow.top;

    // Draw the image onto the mask layer first. Since the mask layer is ALPHA_8, this discards color information.
    // Align it so that when drawn in the end, it originates at targetRect.x, targetRect.y
    // the int casts are very important here - they save us from some slow path for non-integer coords
    shadowMaskBitmapCanvas.drawBitmap(bitmap, (int)Math.round(offsetX * pixelScaleX), (int)Math.round(offsetY * pixelScaleY), null);

    // Prepare the shadow paint. This is the paint that will perform a blur and a tint of the mask
    if (shadowBlurFilter == null || lastShadowBlurRadius != shadow.getRadius()) {
      float scaledRadius = shadow.getRadius() * (pixelScaleX + pixelScaleY) / 2.0f;
      if (scaledRadius > 0) {
        shadowBlurFilter = new BlurMaskFilter(scaledRadius, BlurMaskFilter.Blur.NORMAL);
      } else {
        shadowBlurFilter = null;
      }

      lastShadowBlurRadius = shadow.getRadius();
    }
    shadowPaint.setColor(shadow.getColor());
    if (shadow.getRadius() > 0.0f) {
      shadowPaint.setMaskFilter(shadowBlurFilter);
    } else {
      shadowPaint.setMaskFilter(null);
    }
    shadowPaint.setFilterBitmap(true);

    // Draw the mask onto our shadowBitmap with the shadowPaint. This bitmap now contains the final
    // look of the shadow, correctly positioned inside a rectIncludingShadow-sized area
    // the int casts are very important here - they save us from some slow path for non-integer coords
    shadowBitmapCanvas.drawBitmap(shadowMaskBitmap, (int)Math.round(shadow.getDx() * pixelScaleX), (int)Math.round(shadow.getDy() * pixelScaleY), shadowPaint);

    // Now blit the result onto the final canvas. It might be tempting to skip shadowBitmap and draw the mask
    // directly onto the canvas with shadowPaint, but this breaks the blur, since Paint.setMaskFilter() is not
    // supported on hardware canvases.
    targetCanvas.drawBitmap(shadowBitmap, shadowBitmapSrcRect, intRectIncludingShadow, composePaint);
  }
Renders a shadow (only the shadow) of this.bitmap to the provided canvas.
lottie/src/main/java/com/airbnb/lottie/utils/OffscreenLayer.java