Search code examples
androidandroid-media3

Using android media3 transformer to make color transparent


I would like to set the opacity/transparency of a specified color in a video in android using media3 transformer.

I'm working with the demo app found here: https://developer.android.com/media/media3/transformer/demo-application

The code to modify the video is below but I don't see anything in the documentation to modify the opacity of a color.

private ImmutableList<Effect> createVideoEffectsFromBundle(Bundle bundle) {
    boolean[] selectedEffects =
        bundle.getBooleanArray(ConfigurationActivity.VIDEO_EFFECTS_SELECTIONS);

    if (selectedEffects == null) {
      return ImmutableList.of();
    }

    ImmutableList.Builder<Effect> effects = new ImmutableList.Builder<>();
    if (selectedEffects[ConfigurationActivity.DIZZY_CROP_INDEX]) {
      effects.add(MatrixTransformationFactory.createDizzyCropEffect());
    }
    if (selectedEffects[ConfigurationActivity.EDGE_DETECTOR_INDEX]) {
      try {
        Class<?> clazz = Class.forName("androidx.media3.demo.transformer.MediaPipeShaderProgram");
        Constructor<?> constructor =
            clazz.getConstructor(
                Context.class,
                boolean.class,
                String.class,
                boolean.class,
                String.class,
                String.class);
        effects.add(
            (GlEffect)
                (Context context, boolean useHdr) -> {
                  try {
                    return (GlShaderProgram)
                        constructor.newInstance(
                            context,
                            useHdr,
                            /* graphName= */ "edge_detector_mediapipe_graph.binarypb",
                            /* isSingleFrameGraph= */ true,
                            /* inputStreamName= */ "input_video",
                            /* outputStreamName= */ "output_video");
                  } catch (Exception e) {
                    runOnUiThread(() -> showToast(R.string.no_media_pipe_error));
                    throw new RuntimeException("Failed to load MediaPipeShaderProgram", e);
                  }
                });
      } catch (Exception e) {
        showToast(R.string.no_media_pipe_error);
      }
    }
    if (selectedEffects[ConfigurationActivity.COLOR_FILTERS_INDEX]) {
      switch (bundle.getInt(ConfigurationActivity.COLOR_FILTER_SELECTION)) {
        case ConfigurationActivity.COLOR_FILTER_GRAYSCALE:
          effects.add(RgbFilter.createGrayscaleFilter());
          break;
        case ConfigurationActivity.COLOR_FILTER_INVERTED:
          effects.add(RgbFilter.createInvertedFilter());
          break;
        case ConfigurationActivity.COLOR_FILTER_SEPIA:
          // W3C Sepia RGBA matrix with sRGB as a target color space:
          // https://www.w3.org/TR/filter-effects-1/#sepiaEquivalent
          // The matrix is defined for the sRGB color space and the Transformer library
          // uses a linear RGB color space internally. Meaning this is only for demonstration
          // purposes and it does not display a correct sepia frame.
          float[] sepiaMatrix = {
            0.393f, 0.349f, 0.272f, 0, 0.769f, 0.686f, 0.534f, 0, 0.189f, 0.168f, 0.131f, 0, 0, 0,
            0, 1
          };
          effects.add((RgbMatrix) (presentationTimeUs, useHdr) -> sepiaMatrix);
          break;
        default:
          throw new IllegalStateException(
              "Unexpected color filter "
                  + bundle.getInt(ConfigurationActivity.COLOR_FILTER_SELECTION));
      }
    }
    if (selectedEffects[ConfigurationActivity.MAP_WHITE_TO_GREEN_LUT_INDEX]) {
      int length = 3;
      int[][][] mapWhiteToGreenLut = new int[length][length][length];
      int scale = 255 / (length - 1);
      for (int r = 0; r < length; r++) {
        for (int g = 0; g < length; g++) {
          for (int b = 0; b < length; b++) {
            mapWhiteToGreenLut[r][g][b] =
                Color.rgb(/* red= */ r * scale, /* green= */ g * scale, /* blue= */ b * scale);
          }
        }
      }
      mapWhiteToGreenLut[length - 1][length - 1][length - 1] = Color.GREEN;
      effects.add(SingleColorLut.createFromCube(mapWhiteToGreenLut));
    }
    if (selectedEffects[ConfigurationActivity.RGB_ADJUSTMENTS_INDEX]) {
      effects.add(
          new RgbAdjustment.Builder()
              .setRedScale(bundle.getFloat(ConfigurationActivity.RGB_ADJUSTMENT_RED_SCALE))
              .setGreenScale(bundle.getFloat(ConfigurationActivity.RGB_ADJUSTMENT_GREEN_SCALE))
              .setBlueScale(bundle.getFloat(ConfigurationActivity.RGB_ADJUSTMENT_BLUE_SCALE))
              .build());
    }
    if (selectedEffects[ConfigurationActivity.HSL_ADJUSTMENT_INDEX]) {
      effects.add(
          new HslAdjustment.Builder()
              .adjustHue(bundle.getFloat(ConfigurationActivity.HSL_ADJUSTMENTS_HUE))
              .adjustSaturation(bundle.getFloat(ConfigurationActivity.HSL_ADJUSTMENTS_SATURATION))
              .adjustLightness(bundle.getFloat(ConfigurationActivity.HSL_ADJUSTMENTS_LIGHTNESS))
              .build());
    }
    if (selectedEffects[ConfigurationActivity.CONTRAST_INDEX]) {
      effects.add(new Contrast(bundle.getFloat(ConfigurationActivity.CONTRAST_VALUE)));
    }
    if (selectedEffects[ConfigurationActivity.PERIODIC_VIGNETTE_INDEX]) {
      effects.add(
          (GlEffect)
              (Context context, boolean useHdr) ->
                  new PeriodicVignetteShaderProgram(
                      context,
                      useHdr,
                      bundle.getFloat(ConfigurationActivity.PERIODIC_VIGNETTE_CENTER_X),
                      bundle.getFloat(ConfigurationActivity.PERIODIC_VIGNETTE_CENTER_Y),
                      /* minInnerRadius= */ bundle.getFloat(
                          ConfigurationActivity.PERIODIC_VIGNETTE_INNER_RADIUS),
                      /* maxInnerRadius= */ bundle.getFloat(
                          ConfigurationActivity.PERIODIC_VIGNETTE_OUTER_RADIUS),
                      bundle.getFloat(ConfigurationActivity.PERIODIC_VIGNETTE_OUTER_RADIUS)));
    }
    if (selectedEffects[ConfigurationActivity.SPIN_3D_INDEX]) {
      effects.add(MatrixTransformationFactory.createSpin3dEffect());
    }
    if (selectedEffects[ConfigurationActivity.ZOOM_IN_INDEX]) {
      effects.add(MatrixTransformationFactory.createZoomInTransition());
    }

    @Nullable OverlayEffect overlayEffect = createOverlayEffectFromBundle(bundle, selectedEffects);
    if (overlayEffect != null) {
      effects.add(overlayEffect);
    }

    float scaleX = bundle.getFloat(ConfigurationActivity.SCALE_X, /* defaultValue= */ 1);
    float scaleY = bundle.getFloat(ConfigurationActivity.SCALE_Y, /* defaultValue= */ 1);
    float rotateDegrees =
        bundle.getFloat(ConfigurationActivity.ROTATE_DEGREES, /* defaultValue= */ 0);
    if (scaleX != 1f || scaleY != 1f || rotateDegrees != 0f) {
      effects.add(
          new ScaleAndRotateTransformation.Builder()
              .setScale(scaleX, scaleY)
              .setRotationDegrees(rotateDegrees)
              .build());
    }

    int resolutionHeight =
        bundle.getInt(ConfigurationActivity.RESOLUTION_HEIGHT, /* defaultValue= */ C.LENGTH_UNSET);
    if (resolutionHeight != C.LENGTH_UNSET) {
      effects.add(Presentation.createForHeight(resolutionHeight));
    }

    return effects.build();
  }

Solution

  • MediaCodec, used in Transformer to encode videos, strip out opacity. Therefore, transparency in media3 is only useful when you are overlaying things on top of each other, either by using multiple EditedMediaItemSequences, or by using overlays like BitmapOverlay, or if you're choosing to use VideoFrameProcessor without using the rest of Transformer. Transformer would not be able to let you have non-1 opacity in an output video.

    If you'd just like to set transparency on frames, before they're encoded, there's an AlphaScale effect that allows you to scale an entire frame's transparency, here. If you'd like to scale just the opacity of a color, media3 doesn't currently have support for this. You could implement this by implementing your own GlEffect (and GlShaderProgram) instance, and injecting that into the list of effects to apply to a video. For a full list of GlEffects, please see this query.