图像操作API

图像操作API为自动化流程提供了图片读写、处理以及比较的功能,而这些功能都是基于Image对象实现的。

Image对象

引用方式

在脚本顶部加入下面的脚本引用Image

JavaScript
const { Image } = require('leanpro.visual');

类型定义

Image对象的内容比较多,它同时提供了成员属性、成员方法和静态方法三类内容,使得大部分图像操作都是在Image对象上实现的。

Image对象定义如下:

JavaScript
class Image {
    width: number;
    height: number;
    clip(rect: {x: number, y: number, width: number, height: number}): Image;
    drawImage(image: Image, x: number, y: number): Image;
    filterColor(color: Colors | Colors[]): Promise<Image>;
    getData(option?: {encoding: 'base64' | null}): Promise<Buffer | string>;
    resize(width: number, height: number): Promise<Image>;
    save(filePath: string): Promise<void>;

    static fromData(bufferOrString: Buffer | string): Promise<Image>;
    static fromFile(filePath: string): Promise<Image>;
    static imageEqual(image1: Buffer | string | Image,
        image2: Buffer | string | Image, 
        options?: CompareOptions,
        compareInfo?: ImageCompareInfo): Promise<boolean>;
    static imageCompare(image1: Buffer | string | Image,
        image2: Buffer | string | Image, 
        options?: CompareOptions): Promise<ImageCompareResult>
}

常见用法

Image对象最常见的一个用法就是读取两张图片并进行比较,比较的流程如下:

  1. Image.fromData()Image.fromFile()分别读取两张图片生成各自的Image实例;
  2. Image.imageCompare()比较两个Image对象的差异。 脚本如下:

    JavaScript
    const { Image } = require('leanpro.visual');
      const fs = require('fs');
      (async function() {
          let buf1 = fs.readFileSync('c:/temp/image1.png');
          let image1 = await Image.fromData(buf1);
          console.log(`image1图片的宽为${image1.width}, 高为${image1.height}`);
          
          let image2 = await Image.fromFile('c:/temp/image2.png');
          console.log(`image2图片的宽为${image2.width}, 高为${image2.height}`);
      })()

    Image.fromData()方法可以传入Buffer、base64字符串生成Image实例,具体查看Image.fromData()
    Image.fromFile()方法可以传入一个图片文件的路径来生成Image实例,具体查看Image.fromFile()

上面例子分别从Buffer和文件中读取图片数据并打印宽度和高度。

注意:无法直接通过new Image()的方式创建对象,需要通过fromData或者fromFile来生成Image实例。另外Image对象现只支持PNG格式的图片。

实例属性

width: number;

表示图片的宽度,为一个数字类型。

height: number;

表示图片的高度,为一个数字类型。

实例方法

clip(rect: Rect): Image

用于剪切图片,返回另一个图片Image对象。传入Rect对象(即{x:number, y: number, width: number, height: number}),按照矩形框剪切图片,如果传入数据不合法导致剪切的图片宽度或高度为0,返回为null;

resize(width: number, height: number): Promise<Image>

用于缩放图片,返回另一个图片Image对象。传入新的宽度和高度,会将当前图片缩放至相应的尺寸。如果传入的宽高比例与原图片的宽高比例不一致,则返回的结果也会相应出现扭曲。

filterColor(color: Colors | Colors[]): Promise<Image>

提取图片中的指定颜色,返回一个仅含目标颜色的图片。传入一种或多种颜色,会过滤除了指定颜色以外的所有颜色,并返回新的图片对象。支持的颜色和颜色代号如下:

JavaScript
export enum Colors {
  black = 'black',
  grey = 'grey',
  white = 'white',
  red = 'red',
  orange = 'orange',
  yellow = 'yellow',
  green = 'green',
  cyan = 'cyan', //青色
  blue = 'blue',
  purple = 'purple'
}

提取颜色的脚本可以写作:

JavaScript
let image = await Image.fromFile(filepat)
let imageOnlyRed = await image.filterColor("red"); // 仅保留红色
let imageOnlyYellowGreen = await image.filterColor(["yellow", "green"]); // 仅保留黄色跟绿色

drawImage(image: Image, x: number, y: number): Image;

用于将另一张图片绘制到当前的图片上。返回为新绘制的图片(Image对象)。

例如:

JavaScript
let screenshot1 = await model.getButton("Five").takeScreenshot();
  let screenshot2 = await model.getButton("Six").takeScreenshot();
  let image1 = await Image.fromData(screenshot1);
  let image2 = await Image.fromData(screenshot2);
  
  image1 = image1.clip({ x: 10, y: 10, width: image1.width - 20, height: image1.height - 20 })
  image2 = image2.clip({ x: 10, y: 10, width: image2.width - 20, height: image2.height - 20  })

  let combinedImage = image1.drawImage(image2, image1.width + 5, 0);

上面代码将两个按钮截屏剪切掉10像素的周围边缘,然后绘制到一张图片上,中间间隔5个像素。

如果你想让两幅图片上下排列,你可以使用类似下面的代码:

JavaScript
let combinedImage = image1.drawImage(image2, 0, image1.height + 5);

您还可以使用为负值的x,y,以便第二个图像在第一个图像的另一侧绘制,例如,以下语句将在左侧绘制image2,在右侧绘制image1

JavaScript
let combinedImage = image1.drawImage(image2, -(image1.width + 5), 0);

getData(filePath: string): Promise<void>

返回图片的内容数据,可用于保存至文件,或添加到报表中。注意它返回值类型为Promise,所以在async函数中需要加await。

较常见的一个用法是读取本地图片文件后希望将图片上传到运行报告中作为附件,就可以写作:

JavaScript
let image1 = await Image.fromFile('c:/temp/image1.png');
let image1Buffer = await image1.getData();
this.attach(image1Buffer, 'image/png');

save(filePath: string): Promise<void>

保存图片。传入一个文件路径,会将Image对象保存到目标路径。默认保存为.png文件。注意它的返回值类型为,所以在async函数中需要加await。

静态方法

Image对象静态方法,即不需要进行实例化,引用之后就可以通过Image.[静态方法名]()的方式调用。包含了Image对象的构造方法(如Image.fromData()Image.fromFile())、比较两张图片差别的方法(如Image.imageCompare()Image.imageEqual()等)。

Image.fromData(bufferOrString: Buffer | string): Promise<Image>

从Buffer或base64字符串数据创建一个 Image 对象。

JavaScript
import { Image } from 'leanpro.visual';

const buffer = Buffer.from('image-data');
Image.fromData(buffer).then(image => {
  // do something with the image object
});

Image.fromFile(filePath: string): Promise<Image>

从文件创建一个 Image 对象。

JavaScript
import { Image } from 'leanpro.visual';

const filePath = '/path/to/image.png';
Image.fromFile(filePath).then(image => {
  // do something with the image object
});


图片比较方法

在自动化过程中,为了验证结果的正确性,可以通过验证结果图片和期望图片的差异来判断执行结果是否正确。

目前图片判断提供了两种方式:

  • Image.imageEqual(): 比较图片差异,返回布尔值。
  • Image.imageCompare(): 比较图片差异,返回比较结果对象。这种方式能够取得将两幅图对应像素相减产生的差异结果图片,将差异点用色彩标出来。非常适合将差异图片显示在报表中,可以由人工二次判断是否正确。

由于操作系统、分辨率、颜色设置都可能影响图片的显示,同样的控件在不同的环境设置下可能显示不同,所以在比较截屏图片时,一般会设置比较容忍度。在容忍度阈值内认为两幅图片是相同的。容忍度有如下的分类:

  • 颜色容忍度:针对两幅图片的两个对应像素点,如果它们的RGB颜色按照特定的距离算法在一定的范围内认为是相同的。
  • 像素容忍度:针对两幅图整体而言,如果不同的像素点数量在一定的范围内,则认为两幅图是相同的。像素容忍度还可以根据百分比来设定,即不同的点占图片所有像素的百分比。

Image.imageEqual(image1, image2, options?, compareInfo?): Promise<boolean>

imageEqual用来比较两幅图片是否相同,返回一个布尔值的Promise。如果返回true,表示两幅图片相同;false则表示存在差异。此API适用于快速验证两幅图像是否一致。若需获取展示差异位置的结果图像,请使用imageCompare API。

JavaScript
imageEqual(image1: Buffer | string | Image,
  image2: Buffer | string | Image, 
  options?: CompareOptions,
  compareInfo?: ImageCompareInfo): Promise<boolean>;

  • image1、image2可以是Buffer、base64的字符串,或者一个Image实例。
  • options用来指定图片比较的参数,它有如下的参数设置:

JavaScript
interface CompareOptions {
  colorTolerance?: number,        //default to 0.1
  pixelNumberTolerance?: number,  //no default value
  pixelPercentTolerance?: number, //default 1, means 1% 
  ignoreExtraPart?: boolean       //default to false
  auto?: boolean                  //default to false
}

其中:

  • colorTolerance:颜色容忍度,缺省为0.1。一般不用修改。
  • pixelNumberTolerance:像素数量容忍度,没有缺省值。
  • pixelPercentTolerance:像素百分比容忍度,缺省为1,即1%。例如,两幅图片各有10000像素,允许最多150个像素不同,则设置为1.5。
  • ignoreExtraPart:是否忽略非重叠部分,缺省为false。两幅比较图片可以是不同尺寸,比较时左上角对齐,超出的部分通常被认为是不同的。如果设置为true,则超出部分将被忽略。
  • auto:如果设置为true,则根据图像特征自动识别两幅图片,并将它们缩放到一致的比例进行比较,默认适应到较小尺寸那张图片的比例。

需要注意的是,auto选项不适用于比较包含文本内容的图片。由于系统渲染文字时的差异,文本内容在不同缩放比例下的渲染效果并非简单的等比例放大或缩小,因此在这种情况下,auto选项可能无法提供准确的比较结果。

当pixelNumberTolerance和pixelPercentTolerance有一个超出了设定值(或缺省值)比较就会返回false。如果只需要使用其中一个设定,可以将另一个设定设为比较大的值。例如只需要pixelNumberTolerance,并忽略pixelPercentTolerance,可以把pixelPercentTolerance设成100。

  • 除了返回值表示是否相同外,有时需要知道详细的信息,例如图片尺寸,不同像素的数量和百分比等,这时可以通过传出compareInfo对象获得相关信息。

compareInfo的类型定义如下:

JavaScript
interface ImageCompareInfo {
  image1: { width: number, height: number },
  image2: { width: number, height: number },
  diffPixels: number,        // 不同的像素点数量
  diffPercentage: number     // 不同点占总像素的百分比
}

使用时,如果compareInfo传入一个空的对象,调用imageEqual后compareInfo会填充如上结构的数据。

使用示例:

JavaScript
(async function() {
    try {
      let pngSource = await Image.fromFile(__dirname + '/../source.png');
      let pngTarget = await Image.fromFile(__dirname + '/../target.png');

      let compareInfo = {};
      let isEqual = await Image.imageEqual(pngSource, pngTarget, 
        {pixelNumberTolerance: 300, ignoreExtraPart: true, auto: true}, compareInfo);
      console.log('是否相等?', isEqual);
      console.log('自动填充的compareInfo数据:', JSON.stringify(compareInfo, null, 2));
  } catch(err) {
      console.log(err)
  }
})()

此代码示例展示了如何传入两幅图片进行比较,同时忽略未重叠部分。如果不同的像素点超过300个,则判定两图不同,并输出compareInfo的详细数据。

是否相等? false
自动填充的compareInfo数据为: {
  "image1": {
    "width": 375,
    "height": 266
  },
  "image2": {
    "width": 402,
    "height": 224
  },
  "diffPixels": 3502,
  "diffPercentage": 3.3100814760203408
}

Image.imageCompare(image1, image2, options?): Promise<ImageCompareResult>

imageCompare用于比较两幅图片,返回包含详细信息的数据对象,特别是标示出两幅图片差异部分的结果图像。如果您需要获取显示差异位置的结果图像,请使用此API。

它有如下的签名:

JavaScript
imageCompare(image1: Buffer | string | Image,
  image2: Buffer | string | Image, 
  options?: CompareOptions): Promise<ImageCompareResult>

  • image1image2:可以是Buffer、base64的string,或者一个Image实例。
  • options:可选参数,其设置方式与 imageEqualoptions 设置相同。其中 ignoreExtraPart 参数会影响差异图的生成:若设置为 false,则图片的非重叠部分会以红色标出;若为 true,则不对非重叠部分做特殊处理。

返回值是名为ImpageCompareResult的结构:

JavaScript
interface ImageCompareResult {
  equal: boolean,
  info: ImageCompareInfo,
  diffImage: Image
}

其中:

  • equal表示是否根据容忍度设置认为图片相同。
  • info的结构与imageEqual的compareInfo结构相同,包括像素尺寸,有多少点不同,不同的点的百分比等。
  • diffImage是返回的差分图片的Image对象,相同的像素点以缺省白色表示,不同的点显示为红色。图像中原有的图案会以浅色显示在目标图片中,方便定位差异部分所在的位置。

这两个图片比较API中,imageEqual是imageCompare的封装,为了更直观的返回两个图片是否相等的判断结果。如果只想知道是否相等就用imageEqual,如果除了是否相等外,还想知道更详细的就用imageCompare。

使用示例

以下示例展示如何比较两张图片并生成差异图像:

JavaScript
(async function() {
  try {
    let pngSource = await Image.fromFile(__dirname + '/../image1.png');
    let pngTarget = await Image.fromFile(__dirname + '/../image2.png');

    let result = await Image.imageCompare(pngSource, pngTarget, {pixelNumberTolerance: 300, auto: true});
    let diffImage = result.diffImage;
    console.log('resultMeta', JSON.stringify(result.info, null, 2));
    let imageData = await diffImage.getData();
    fs.writeFileSync(__dirname + '/../diff.png', imageData);
  } catch(err) {
    console.log(err);
  }
})();

此代码比较两幅图片,打印差异信息,并将差分图像保存为"diff.png"文件。

假如我们有下面两幅图片:

image1 (375 * 266) image2 (402 * 224)

根据参数的不同,可以生成下面的差分图片:

ignoreExtraPart = true ignoreExtraPart = false

左边是ignoreExtraPart = true的情况,超出部分也标记为红色,右边是ignoreExtraPart = false,忽略了超出部分。

控件与模型图片比较

Windows自动化中一个常见的场景是,将运行时控件的截屏与模型中的保存的对象截屏相比较。这可以通过调用测试对象的modelImage方法获得测试对象的截屏png图片,以base64字符串数据返回。如果模型中该对象没有对应的截屏图片,则返回null。

下面的样例从对象模型中获取按钮对象,同时拿到控件截屏和模型中的截屏,并做对比,将对比信息打印出来,同时将差分图片保存到目录中。

JavaScript
const { Image } = require('leanpro.visual');
const { WinAuto } = require('leanpro.win');
const fs = require('fs');

const model = WinAuto.loadModel(__dirname + "\\test.tmodel");

(async function () {
  let five = model.getButton("Five");
  let controlImage = await five.takeScreenshot();
  let modelImage = await five.modelImage();
  let result = await Image.imageCompare(modelImage, controlImage);
  fs.writeFileSync(__dirname + '/diff.png', await result.diffImage.getData());
  //print the diff information
  console.log(result.info)
})();

results matching ""

    No results matching ""