在 Android 上管理演示文稿中的图像变换效果

概述

Aspose.Slides 将图片调整表示为有序的图像变换操作集合。对于图片框,首先获取框的 ISlidesPicture 并访问 ISlidesPicture.getImageTransform。返回的 IImageTransformOperationCollection 允许追加、枚举、检查、移除和清除效果,而无需重新写入原始图像字节。

本文展示了完整的工作流,包括亮度和对比度、颜色变换、模糊、透明度、有序效果链、有效值、移除以及 PPTX 循环验证。

了解效果所有权和图像复用

图像资源和显示它的图片是不同的对象:

  • IPPImage 存储或引用演示文稿拥有的源图像数据。
  • ISlidesPicture 属于图片填充,引用图像资源并保存图像变换集合。
  • IPictureFrame 是拥有相应图片填充、几何、裁剪设置以及其它框级格式的幻灯片形状。

因此,图像变换操作不会修改 IPPImage 中的字节。当同一 IPPImage 多次传递给 IShapeCollection.addPictureFrame 时,每个新图片框都会获得其自己的 ISlidesPicture 和其自己的变换集合。对一个框应用灰度不会使其他框也变灰,尽管它们复用了同一嵌入图像资源。

相同的 ISlidesPicture.getImageTransform 模型也被其它图片填充使用,例如形状或幻灯片背景。以下示例侧重于图片框。

使用有效的参数范围和单位

演示的方法使用以下语义范围和单位。即使特定库版本不会立即拒绝每个超出范围的值,也请保持在这些范围内;目标演示文稿格式可能在保存时或 PowerPoint 打开文件时对无效数据进行标准化、忽略或拒绝。

操作 参数 有效范围和单位
addBrightnessContrastEffect brightness, contrast -100100,百分比;0 表示保持该组件不变。
addGrayScaleEffect 无数值参数。Alpha 保持不变。
addDuotoneEffect color1, color2 两个用于暗像素和亮像素的颜色。android.graphics.Color 使用的 RGB 和 alpha 通道值范围为 0255
addTintEffect hue, amount hue 为 0(含)到 360(不含)度;amount 为 -100100,百分比。
addHSLEffect hue, saturation, luminance hue 为 0(含)到 360(不含)度;saturation 和 luminance 为 -100100,百分比。
addColorReplaceEffect color 替换颜色的通道值范围为 0255。已有的 alpha 值保持不变。
addBlurEffect radius, grow radius 为非负数,单位为点;grow 为布尔值,控制模糊内容是否可以超出原始边界。
addAlphaModulateFixedEffect amount 非负百分比。使用 0100 表示普通的不透明度缩放:0 为完全透明,100 保持现有 alpha。
addAlphaReplaceEffect alpha 0100,百分比不透明度。
addAlphaBiLevelEffect threshold 0100,百分比 alpha 阈值。低于阈值的像素变为透明,等于或高于阈值的像素变为不透明。

对于固定 alpha 调制,透明度和不透明度是互补的。例如,35% 透明度对应 65% 的 alpha 调制量。

应用亮度和对比度

IImageTransformOperationCollection.addBrightnessContrastEffect 返回一个 IBrightnessContrast 操作。其标量设置在创建操作时提供。IBrightnessContrast.getEffective 返回只读的计算值,可用于检查或记录。

下面的示例将亮度提高 15%,对比度提高 20%,然后在不修改嵌入图像的情况下渲染预览:

import com.aspose.slides.*;

Presentation presentation = new Presentation();
try {
    ISlide slide = presentation.getSlides().get_Item(0);

    IPPImage image;
    IImage sourceImage = Images.fromFile("photo.png");
    try {
        image = presentation.getImages().addImage(sourceImage);
    } finally {
        sourceImage.dispose();
    }
    IPictureFrame pictureFrame = slide.getShapes().addPictureFrame(ShapeType.Rectangle, 50, 50, 400, 260, image);

    IImageTransformOperationCollection imageTransform = pictureFrame.getPictureFormat().getPicture().getImageTransform();
    IBrightnessContrast brightnessContrast = imageTransform.addBrightnessContrastEffect(15f, 20f);

    IBrightnessContrastEffectiveData effectiveValues = brightnessContrast.getEffective();
    System.out.println("Brightness: " + effectiveValues.getBrightness() + "%");
    System.out.println("Contrast: " + effectiveValues.getContrast() + "%");

    IImage preview = slide.getImage();
    try {
        preview.save("brightness-contrast-preview.png", ImageFormat.Png);
    } finally {
        preview.dispose();
    }
} finally {
    presentation.dispose();
}

BrightnessContrast 是 Office 2010 的图片效果扩展,便携性低于标准 DrawingML 亮度效果。当亮度和对比度必须在 PPTX 循环后仍可编辑时,请使用 IImageTransformOperationCollection.addLuminanceEffect 并在重新打开文件后验证结果。格式限制章节对该区别有更详细的说明。

应用颜色变换

颜色效果可以独立应用于复用同一图像资源的不同图片框。下面的示例创建五个框,并分别应用灰度、双音、色调、HSL 调整和颜色替换。

IDuotone 包含两个可独立编辑的颜色参数:color1 映射暗像素,color2 映射亮像素。这使其成为示例,展示了设置比单一标量更复杂的效果。

import com.aspose.slides.*;
import android.graphics.Color;

Presentation presentation = new Presentation();
try {
    ISlide slide = presentation.getSlides().get_Item(0);

    IPPImage image;
    IImage sourceImage = Images.fromFile("photo.png");
    try {
        image = presentation.getImages().addImage(sourceImage);
    } finally {
        sourceImage.dispose();
    }

    IPictureFrame grayFrame = slide.getShapes().addPictureFrame(ShapeType.Rectangle, 20, 20, 180, 120, image);
    grayFrame.getPictureFormat().getPicture().getImageTransform().addGrayScaleEffect();

    IPictureFrame duotoneFrame = slide.getShapes().addPictureFrame(ShapeType.Rectangle, 220, 20, 180, 120, image);
    IDuotone duotone = duotoneFrame.getPictureFormat().getPicture().getImageTransform().addDuotoneEffect();
    duotone.getColor1().setColor(Color.rgb(0, 0, 128));
    duotone.getColor2().setColor(Color.rgb(255, 215, 0));

    IPictureFrame tintFrame = slide.getShapes().addPictureFrame(ShapeType.Rectangle, 420, 20, 180, 120, image);
    tintFrame.getPictureFormat().getPicture().getImageTransform().addTintEffect(210f, 35f);

    IPictureFrame hslFrame = slide.getShapes().addPictureFrame(ShapeType.Rectangle, 120, 170, 180, 120, image);
    hslFrame.getPictureFormat().getPicture().getImageTransform().addHSLEffect(30f, 20f, -10f);

    IPictureFrame replacementFrame = slide.getShapes().addPictureFrame(ShapeType.Rectangle, 320, 170, 180, 120, image);
    IColorReplace colorReplacement = replacementFrame.getPictureFormat().getPicture().getImageTransform().addColorReplaceEffect();
    colorReplacement.getColor().setColor(Color.rgb(100, 149, 237));

    presentation.save("color-transformations.pptx", SaveFormat.Pptx);
} finally {
    presentation.dispose();
}

addColorReplaceEffect 将每个像素的颜色替换为固定颜色,同时保留 alpha。它与 addColorChangeEffect 不同,后者将一种源颜色映射到另一种颜色,并公开源与目标颜色的格式。

添加模糊、透明度和 Alpha 效果

addBlurEffect 会影响所有颜色通道,包括 alpha。当模糊边缘可能超出原始图片边界时,将 grow 设置为 true

要实现均匀透明度,请使用 addAlphaModulateFixedEffect。它会乘以每个现有的 alpha 值,从而保持部分透明像素的相对差异。addAlphaReplaceEffect 则为所有像素分配统一的 alpha 值。addAlphaBiLevelEffect 根据阈值将 alpha 转换为两个级别。

import com.aspose.slides.*;

Presentation presentation = new Presentation();
try {
    ISlide slide = presentation.getSlides().get_Item(0);

    IPPImage image;
    IImage sourceImage = Images.fromFile("photo.png");
    try {
        image = presentation.getImages().addImage(sourceImage);
    } finally {
        sourceImage.dispose();
    }

    IPictureFrame blurredFrame = slide.getShapes().addPictureFrame(ShapeType.Rectangle, 20, 20, 200, 140, image);
    IBlur blur = blurredFrame.getPictureFormat().getPicture().getImageTransform().addBlurEffect(4.5, true);
    blur.setRadius(5);

    IPictureFrame transparentFrame = slide.getShapes().addPictureFrame(ShapeType.Rectangle, 240, 20, 200, 140, image);
    IAlphaModulateFixed alphaModulate = transparentFrame.getPictureFormat().getPicture().getImageTransform().addAlphaModulateFixedEffect(65f);
    alphaModulate.setAmount(60f);

    IPictureFrame uniformAlphaFrame = slide.getShapes().addPictureFrame(ShapeType.Rectangle, 20, 180, 200, 140, image);
    uniformAlphaFrame.getPictureFormat().getPicture().getImageTransform().addAlphaReplaceEffect(55f);

    IPictureFrame binaryAlphaFrame = slide.getShapes().addPictureFrame(ShapeType.Rectangle, 240, 180, 200, 140, image);
    IAlphaBiLevel alphaBiLevel = binaryAlphaFrame.getPictureFormat().getPicture().getImageTransform().addAlphaBiLevelEffect(50f);
    alphaBiLevel.setThreshold(45f);
    binaryAlphaFrame.getPictureFormat().getPicture().getImageTransform().addAlphaInverseEffect();

    presentation.save("blur-and-alpha-effects.pptx", SaveFormat.Pptx);
} finally {
    presentation.dispose();
}

其他无参数的 alpha 操作包括 addAlphaCeilingEffect(将所有非零 alpha 设为完全不透明),addAlphaFloorEffect(将所有低于 100% 的 alpha 设为完全透明),以及 addAlphaInverseEffect(将 alpha 改为 100% - alpha)。

构建有序效果链

每个 add...Effect 方法会将新操作追加到集合的末尾。渲染器将集合视为有序管道:操作 0 的输出成为操作 1 的输入,依此类推。因此,以不同顺序排列相同操作会产生不同的图像。

例如,先灰度后色调会先去除色彩信息再重新着色亮度结果;先色调后灰度会再次去除色调。类似地,Alpha 替换可以覆盖前面操作计算的 alpha,而 Alpha 调制会保留它们的相对差异。

下面的示例构建四操作链,将其保存为 PPTX,重新打开演示文稿,检查操作类型及其顺序,并渲染重新打开的结果:

import com.aspose.slides.*;

Presentation presentation = new Presentation();
try {
    ISlide slide = presentation.getSlides().get_Item(0);
    IPPImage image;
    IImage sourceImage = Images.fromFile("photo.png");
    try {
        image = presentation.getImages().addImage(sourceImage);
    } finally {
        sourceImage.dispose();
    }
    IPictureFrame pictureFrame = slide.getShapes().addPictureFrame(ShapeType.Rectangle, 50, 50, 400, 260, image);

    IImageTransformOperationCollection imageTransform = pictureFrame.getPictureFormat().getPicture().getImageTransform();
    imageTransform.addGrayScaleEffect();
    imageTransform.addTintEffect(220f, 25f);
    imageTransform.addBlurEffect(2.5, false);
    imageTransform.addAlphaModulateFixedEffect(80f);

    presentation.save("image-transform-chain.pptx", SaveFormat.Pptx);
} finally {
    presentation.dispose();
}

Presentation reopenedPresentation = new Presentation("image-transform-chain.pptx");
try {
    IShape reopenedShape = reopenedPresentation.getSlides().get_Item(0).getShapes().get_Item(0);

    if (reopenedShape instanceof IPictureFrame) {
        IPictureFrame reopenedFrame = (IPictureFrame) reopenedShape;
        IImageTransformOperationCollection reopenedTransform = reopenedFrame.getPictureFormat().getPicture().getImageTransform();
        boolean orderIsPreserved = reopenedTransform.size() == 4 && 
                reopenedTransform.get_Item(0) instanceof IGrayScale && 
                reopenedTransform.get_Item(1) instanceof ITint && 
                reopenedTransform.get_Item(2) instanceof IBlur && 
                reopenedTransform.get_Item(3) instanceof IAlphaModulateFixed;
        System.out.println(orderIsPreserved ? "The effect chain was preserved." : "The effect chain changed during the round trip.");

        IImage renderedSlide = reopenedPresentation.getSlides().get_Item(0).getImage();
        try {
            renderedSlide.save("reopened-effect-chain.png", ImageFormat.Png);
        } finally {
            renderedSlide.dispose();
        }
    } else {
        System.out.println("The reopened shape is not a picture frame.");
    }
} finally {
    reopenedPresentation.dispose();
}

该集合并未强制使用兼容矩阵来限制颜色、alpha 和模糊操作必须在不同链中。它们可以组合,但并非所有组合都有用。固定颜色替换会移除之前颜色效果产生的 RGB 变化;双音后再灰度会去除两个选定颜色;Alpha ceiling、floor、replace 或 bi‑level 操作会丢弃之前创建的 alpha 细节。请根据所需的像素处理顺序构建链,而不是把其项目当作无序的格式标志。

检查可编辑和有效值

可编辑的操作是存储在 ISlidesPicture.getImageTransform 中的对象。根据效果的不同,它可能直接公开可写成员。例如,IBlur 公开可写的 radiusgrowIAlphaModulateFixed 公开可写的 amount,以及 IAlphaBiLevel 公开可写的 threshold。像 IDuotone 这样的颜色效果会公开可变的 IColorFormat 对象。

某些操作接口,包括 IBrightnessContrastIHSLITintIAlphaReplace,不公开其创建时的标量为可写属性。要更改这些设置,需要移除该操作并在所需位置添加替代操作。

getEffective() 返回的有效数据是计算后的只读值。它对解析主题依赖的颜色和读取渲染器使用的标准化值很有帮助,但不是另一个编辑界面。下面的示例枚举链并检查在对应 API 提供的有效值:

import com.aspose.slides.*;

Presentation presentation = new Presentation("image-transform-chain.pptx");
try {
    IPictureFrame pictureFrame = null;

    for (IShape shape : presentation.getSlides().get_Item(0).getShapes()) {
        if (shape instanceof IPictureFrame) {
            pictureFrame = (IPictureFrame) shape;
            break;
        }
    }

    if (pictureFrame != null) {
        IImageTransformOperationCollection imageTransform = pictureFrame.getPictureFormat().getPicture().getImageTransform();

        for (int index = 0; index < imageTransform.size(); index++) {
            IImageTransformOperation operation = imageTransform.get_Item(index);
            System.out.println(index + ": " + operation.getClass().getSimpleName());

            if (operation instanceof IBrightnessContrast) {
                IBrightnessContrastEffectiveData data = ((IBrightnessContrast) operation).getEffective();
                System.out.println("  Brightness: " + data.getBrightness());
                System.out.println("  Contrast: " + data.getContrast());
            } else if (operation instanceof ILuminance) {
                ILuminanceEffectiveData data = ((ILuminance) operation).getEffective();
                System.out.println("  Brightness: " + data.getBrightness());
                System.out.println("  Contrast: " + data.getContrast());
            } else if (operation instanceof IDuotone) {
                IDuotoneEffectiveData data = ((IDuotone) operation).getEffective();
                System.out.println("  Dark color: " + data.getColor1());
                System.out.println("  Light color: " + data.getColor2());
            } else if (operation instanceof IColorReplace) {
                IColorReplaceEffectiveData data = ((IColorReplace) operation).getEffective();
                System.out.println("  Replacement color: " + data.getColor());
            } else if (operation instanceof IHSL) {
                IHSLEffectiveData data = ((IHSL) operation).getEffective();
                System.out.println("  HSL: " + data.getHue() + ", " + data.getSaturation() + ", " + data.getLuminance());
            } else if (operation instanceof ITint) {
                ITintEffectiveData data = ((ITint) operation).getEffective();
                System.out.println("  Tint: " + data.getHue() + ", " + data.getAmount());
            } else if (operation instanceof IBlur) {
                IBlurEffectiveData data = ((IBlur) operation).getEffective();
                System.out.println("  Blur radius: " + data.getRadius() + " pt");
            } else if (operation instanceof IAlphaModulateFixed) {
                IAlphaModulateFixedEffectiveData data = ((IAlphaModulateFixed) operation).getEffective();
                System.out.println("  Alpha amount: " + data.getAmount() + "%");
            } else if (operation instanceof IAlphaReplace) {
                IAlphaReplaceEffectiveData data = ((IAlphaReplace) operation).getEffective();
                System.out.println("  Replacement alpha: " + data.getAlpha() + "%");
            } else if (operation instanceof IAlphaBiLevel) {
                IAlphaBiLevelEffectiveData data = ((IAlphaBiLevel) operation).getEffective();
                System.out.println("  Alpha threshold: " + data.getThreshold() + "%");
            }
        }
    }
} finally {
    presentation.dispose();
}

没有参数的效果如灰度、alpha ceiling 和 alpha inverse 仍然会有有效数据对象,只是没有可打印的标量设置。它们在集合中的存在和位置才是重要信息。

移除或清除图像变换

使用 IImageTransformOperationCollection.removeAt 按索引移除单个操作。由于移除后索引会变化,请先搜索目标并在枚举后移除。使用 ImageTransformOperationCollection.clear 可清除整条链。

import com.aspose.slides.*;

Presentation presentation = new Presentation("image-transform-chain.pptx");
try {
    IPictureFrame pictureFrame = null;

    for (IShape shape : presentation.getSlides().get_Item(0).getShapes()) {
        if (shape instanceof IPictureFrame) {
            pictureFrame = (IPictureFrame) shape;
            break;
        }
    }

    if (pictureFrame != null) {
        IImageTransformOperationCollection imageTransform = pictureFrame.getPictureFormat().getPicture().getImageTransform();
        int blurIndex = -1;

        for (int index = 0; index < imageTransform.size(); index++) {
            if (imageTransform.get_Item(index) instanceof IBlur) {
                blurIndex = index;
                break;
            }
        }

        if (blurIndex >= 0) {
            imageTransform.removeAt(blurIndex);
            System.out.println("The blur operation was removed.");
        }

        imageTransform.clear();
        System.out.println("Remaining operations: " + imageTransform.size());
        presentation.save("image-transforms-cleared.pptx", SaveFormat.Pptx);
    }
} finally {
    presentation.dispose();
}

移除或清除变换仅改变图片格式,不会删除、重新压缩或以其他方式修改复用的 IPPImage 资源。

考虑演示文稿格式和导出目标

图像变换源自 DrawingML,因此 PPTX 是效果链的首选可编辑格式。即使使用 PPTX,也不是所有操作都具备相同的可移植性:

  • 标准 DrawingML 操作如 luminance、grayscale、duotone、tint、HSL、blur 以及常见的 alpha 操作最有可能在 PPTX 循环后保持不变。始终重新打开生成的文件并检查集合,以确保需要保留时的完整性。
  • BrightnessContrast 是 Office 2010 的扩展,而非标准 DrawingML luminance 操作。它可用于内存渲染,但保存并重新打开 PPTX 后不一定仍为可编辑的 IBrightnessContrast。请优先使用 addLuminanceEffect 实现持久的亮度和对比度调整。
  • 二进制 PPT 格式早于完整的 DrawingML 效果模型。保存为 PPT 可能会省略不支持的操作、将链缩减为支持的子集,或近似外观。不要使用 PPT 作为复杂可编辑链的验证格式。
  • 渲染为 PNG、JPEG、TIFF、PDF、SVG、HTML 或其他可视输出时,会将支持的链应用于渲染后的外观。这些输出不包含可编辑的 IImageTransformOperationCollection;光栅格式会将结果展平成像素,文档/矢量导出会存储各自的渲染表示。
  • 效果不会使链接的图像变为自包含。渲染链接图片仍然依赖于加载演示文稿时链接资源的可用性。

不同的演示文稿使用者在边缘情况的渲染上可能会有所差异,尤其是当多种 alpha 或颜色量化操作组合时。对于关键输出,请使用生产环境中相同版本的 Aspose.Slides,既测试可编辑的循环,又测试最终导出格式。

常见问题

图像变换效果会修改嵌入的图像数据吗?

不会。操作属于图片填充使用的 ISlidesPicture,底层 IPPImage 的字节保持不变。

复用同一图像的两个图片框会共享它们的效果吗?

不会。复用 IPPImage 可以避免重复的图像数据,但每个图片框通常拥有单独的 ISlidesPicture 和图像变换集合。

可以组合颜色、模糊和 alpha 效果吗?

可以。集合接受它们在同一有序链中。请考虑每个操作对前一个操作输出的影响,因为替换和阈值操作可能会丢弃之前的颜色或 alpha 细节。

为什么有效值是只读的?

有效数据代表用于渲染的计算值,包括已解析的颜色。请在变换集合中编辑那些具有可写成员的操作;否则移除该操作并使用新的创建参数添加替代操作。

应该使用哪种格式来保留变换链?

使用 PPTX 并通过重新打开文件进行验证。传统 PPT 无法完整表示 DrawingML 效果模型,渲染导出格式仅保留外观而不保留可编辑的变换操作。