176e6818aSopenharmony_ci#!/usr/bin/env python3
276e6818aSopenharmony_ci# coding=utf-8
376e6818aSopenharmony_ci
476e6818aSopenharmony_ci#
576e6818aSopenharmony_ci# Copyright (c) 2022 Huawei Device Co., Ltd.
676e6818aSopenharmony_ci# Licensed under the Apache License, Version 2.0 (the "License");
776e6818aSopenharmony_ci# you may not use this file except in compliance with the License.
876e6818aSopenharmony_ci# You may obtain a copy of the License at
976e6818aSopenharmony_ci#
1076e6818aSopenharmony_ci#     http://www.apache.org/licenses/LICENSE-2.0
1176e6818aSopenharmony_ci#
1276e6818aSopenharmony_ci# Unless required by applicable law or agreed to in writing, software
1376e6818aSopenharmony_ci# distributed under the License is distributed on an "AS IS" BASIS,
1476e6818aSopenharmony_ci# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1576e6818aSopenharmony_ci# See the License for the specific language governing permissions and
1676e6818aSopenharmony_ci# limitations under the License.
1776e6818aSopenharmony_ci#
1876e6818aSopenharmony_ci
1976e6818aSopenharmony_ciimport enum
2076e6818aSopenharmony_ciimport time
2176e6818aSopenharmony_ci
2276e6818aSopenharmony_citry:
2376e6818aSopenharmony_ci    from PIL import Image
2476e6818aSopenharmony_ci    from PIL import ImageDraw
2576e6818aSopenharmony_ciexcept ImportError:
2676e6818aSopenharmony_ci    pass
2776e6818aSopenharmony_cifrom devicetest.utils.time_util import TS
2876e6818aSopenharmony_ci
2976e6818aSopenharmony_ci
3076e6818aSopenharmony_ciclass ImgLoc(enum.Enum):
3176e6818aSopenharmony_ci    '''
3276e6818aSopenharmony_ci    图片所在的枚举类
3376e6818aSopenharmony_ci    1左上 (North_West)
3476e6818aSopenharmony_ci    2中上 (North)
3576e6818aSopenharmony_ci    3右上 (Nort_East)
3676e6818aSopenharmony_ci    4左中 (West)
3776e6818aSopenharmony_ci    5全图 (All)
3876e6818aSopenharmony_ci    6右中 (East)
3976e6818aSopenharmony_ci    7左下 (South_West)
4076e6818aSopenharmony_ci    8中下 (South)
4176e6818aSopenharmony_ci    9右下(South_East)
4276e6818aSopenharmony_ci    '''
4376e6818aSopenharmony_ci
4476e6818aSopenharmony_ci    # 在大图的左上内
4576e6818aSopenharmony_ci    North_West = 1
4676e6818aSopenharmony_ci    # 代表子图部分全部位于大图的水平中线以上
4776e6818aSopenharmony_ci    North = 2
4876e6818aSopenharmony_ci    # 在大图的右上内
4976e6818aSopenharmony_ci    North_East = 3
5076e6818aSopenharmony_ci    # 代表子图部分全部位于大图的垂直中线左
5176e6818aSopenharmony_ci    West = 4
5276e6818aSopenharmony_ci    # 全图查找,当小图跟大图的水平中线, 且与锤子中线都有交集,就只能用该参数了
5376e6818aSopenharmony_ci    All = 5
5476e6818aSopenharmony_ci    # 代表子图部分全部位于大图的垂直中线右
5576e6818aSopenharmony_ci    East = 6
5676e6818aSopenharmony_ci    # 在大图的左下内
5776e6818aSopenharmony_ci    South_West = 7
5876e6818aSopenharmony_ci    # 代表子图部分全部位于大图的水平中线以下
5976e6818aSopenharmony_ci    South = 8
6076e6818aSopenharmony_ci    # 在大图的右下内
6176e6818aSopenharmony_ci    South_East = 9
6276e6818aSopenharmony_ci
6376e6818aSopenharmony_ci
6476e6818aSopenharmony_ciclass ImgUtils:
6576e6818aSopenharmony_ci    @staticmethod
6676e6818aSopenharmony_ci    def img2arr(arr_img, rect=None, convert=True):
6776e6818aSopenharmony_ci        '''
6876e6818aSopenharmony_ci        @将位图流转化为二维二值数组
6976e6818aSopenharmony_ci        @param arr_img: instance of Image
7076e6818aSopenharmony_ci        '''
7176e6818aSopenharmony_ci        if convert and arr_img.mode != 'L':
7276e6818aSopenharmony_ci            arr_img = arr_img.convert('L')
7376e6818aSopenharmony_ci
7476e6818aSopenharmony_ci        width, height = arr_img.size
7576e6818aSopenharmony_ci        pix = arr_img.load()
7676e6818aSopenharmony_ci        if rect:
7776e6818aSopenharmony_ci            rect_l, rect_t, rect_r, rect_b = rect
7876e6818aSopenharmony_ci            result_list = []
7976e6818aSopenharmony_ci            for pix_h in range(height):
8076e6818aSopenharmony_ci                if not rect_t <= pix_h <= rect_b + 1:
8176e6818aSopenharmony_ci                    continue
8276e6818aSopenharmony_ci                temp_list = []
8376e6818aSopenharmony_ci                for pix_w in range(width):
8476e6818aSopenharmony_ci                    if not rect_l <= pix_w <= rect_r + 1:
8576e6818aSopenharmony_ci                        continue
8676e6818aSopenharmony_ci                    temp_list.append(pix[pix_w, pix_h])
8776e6818aSopenharmony_ci                result_list.append(temp_list)
8876e6818aSopenharmony_ci            return result_list
8976e6818aSopenharmony_ci
9076e6818aSopenharmony_ci        result_list = []
9176e6818aSopenharmony_ci        for pix_h in range(height):
9276e6818aSopenharmony_ci            temp_list = []
9376e6818aSopenharmony_ci            for pix_w in range(width):
9476e6818aSopenharmony_ci                temp_list.append(pix[pix_w, pix_h])
9576e6818aSopenharmony_ci            result_list.append(temp_list)
9676e6818aSopenharmony_ci
9776e6818aSopenharmony_ci        return result_list
9876e6818aSopenharmony_ci
9976e6818aSopenharmony_ci    @staticmethod
10076e6818aSopenharmony_ci    def get_rect(rect_width, rect_height, location):
10176e6818aSopenharmony_ci        '''
10276e6818aSopenharmony_ci        根据方位对象,获取图片的方位
10376e6818aSopenharmony_ci        '''
10476e6818aSopenharmony_ci        rect = (0, 0, rect_width, rect_height)
10576e6818aSopenharmony_ci        if location == ImgLoc.East.value:
10676e6818aSopenharmony_ci            rect = (int(rect_width >> 1), 0, rect_width, rect_height)
10776e6818aSopenharmony_ci        elif location == ImgLoc.South.value:
10876e6818aSopenharmony_ci            rect = (0, int(rect_height >> 1), rect_width, rect_height)
10976e6818aSopenharmony_ci        elif location == ImgLoc.West.value:
11076e6818aSopenharmony_ci            rect = (0, 0, int(rect_width >> 1), rect_height)
11176e6818aSopenharmony_ci        elif location == ImgLoc.North.value:
11276e6818aSopenharmony_ci            rect = (0, 0, rect_width, int(rect_height >> 1))
11376e6818aSopenharmony_ci
11476e6818aSopenharmony_ci        elif location == ImgLoc.North_East.value:
11576e6818aSopenharmony_ci            rect = (int(rect_width >> 1), 0,
11676e6818aSopenharmony_ci                    rect_width, int(rect_height >> 1))
11776e6818aSopenharmony_ci        elif location == ImgLoc.South_East.value:
11876e6818aSopenharmony_ci            rect = (int(rect_width >> 1),
11976e6818aSopenharmony_ci                    int(rect_height >> 1), rect_width, rect_height)
12076e6818aSopenharmony_ci        elif location == ImgLoc.North_West.value:
12176e6818aSopenharmony_ci            rect = (0, 0, int(rect_width >> 1), int(rect_height >> 1))
12276e6818aSopenharmony_ci        elif location == ImgLoc.South_West.value:
12376e6818aSopenharmony_ci            rect = (0, int(rect_height >> 1),
12476e6818aSopenharmony_ci                    int(rect_width >> 1), rect_height)
12576e6818aSopenharmony_ci
12676e6818aSopenharmony_ci        return rect
12776e6818aSopenharmony_ci
12876e6818aSopenharmony_ci    @staticmethod
12976e6818aSopenharmony_ci    def quick_find(fp1, fp2, similar=1, density=None,
13076e6818aSopenharmony_ci                   rect=None, loc=ImgLoc.All.value, debug=False):
13176e6818aSopenharmony_ci        '''
13276e6818aSopenharmony_ci        快速查找图片,指定的similar越大,速度越快
13376e6818aSopenharmony_ci        1. 如果similar不等于1,则使用density来加快查找速度 density(x,y)
13476e6818aSopenharmony_ci        表示对比的时候 每个横坐标上只对比 (width / 2^x)个点
13576e6818aSopenharmony_ci        每个纵坐标上只对比 height / 2^y个点,相当于只对比原来的
13676e6818aSopenharmony_ci        (width + height) / 2^(x+y) 个点
13776e6818aSopenharmony_ci        @param fp1: 大图片的绝对路径
13876e6818aSopenharmony_ci        @param fp2: 小图片的绝对路径
13976e6818aSopenharmony_ci        @param similar: 对比的相似度;如 0.7, 0.9, 1
14076e6818aSopenharmony_ci        @param density: 小图中对比的密度: (2,3) 表示每行对比 width >> 2个点;
14176e6818aSopenharmony_ci        没列对比 height >> 3个点
14276e6818aSopenharmony_ci        @param rect: 大图中指定区域内查找 (left,top,right,bottom)
14376e6818aSopenharmony_ci        @param loc: 小图在大图中的什么部位,是一个枚举对象,ImgLoc,注意需要
14476e6818aSopenharmony_ci        加.value;如: ImgLoc.North.value 或者 直接输入 1 -9 的数字也行
14576e6818aSopenharmony_ci        @param debug: 是否打印debug信息
14676e6818aSopenharmony_ci        '''
14776e6818aSopenharmony_ci        if debug:
14876e6818aSopenharmony_ci            TS.start()
14976e6818aSopenharmony_ci        _m1 = Image.open(fp1)
15076e6818aSopenharmony_ci        _m2 = Image.open(fp2)
15176e6818aSopenharmony_ci
15276e6818aSopenharmony_ci        m1_w, m1_h = _m1.size
15376e6818aSopenharmony_ci        if not rect:
15476e6818aSopenharmony_ci            rect = ImgUtils.get_rect(m1_w, m1_h, loc)
15576e6818aSopenharmony_ci
15676e6818aSopenharmony_ci        data1 = ImgUtils.img2arr(_m1.crop(rect) if rect else _m1)
15776e6818aSopenharmony_ci        data2 = ImgUtils.img2arr(_m2)
15876e6818aSopenharmony_ci        if debug:
15976e6818aSopenharmony_ci            TS.stop("before find_arr")
16076e6818aSopenharmony_ci        return ImgUtils.find_arr(data1, data2, similar, density, rect, debug)
16176e6818aSopenharmony_ci
16276e6818aSopenharmony_ci    @staticmethod
16376e6818aSopenharmony_ci    def find_arr(im1, im2, similar=1, density=None, rect=None, debug=False):
16476e6818aSopenharmony_ci        '''
16576e6818aSopenharmony_ci        在大图中查找小图
16676e6818aSopenharmony_ci        注意:如果density值为None,则系统自动设置,保证特征点在9 - 16个左右
16776e6818aSopenharmony_ci        (即 3 * 3 或 4 * 4之间)
16876e6818aSopenharmony_ci        @param im1 大图的二维数组
16976e6818aSopenharmony_ci        @param im2 小图的二维数组
17076e6818aSopenharmony_ci        @param similar 相似度
17176e6818aSopenharmony_ci        @param density (x,y)  x: 可以控制小图横坐标查找的点数
17276e6818aSopenharmony_ci        im2Width >> x 个点数
17376e6818aSopenharmony_ci        @param rect 在指定的区域中查找图片 (若指定,则可以大大节省时间)
17476e6818aSopenharmony_ci        (leftX,topY,rihgtX,bottomY)
17576e6818aSopenharmony_ci        @return (rect,similar) rect:找到的图片位置; similar:相似度
17676e6818aSopenharmony_ci        '''
17776e6818aSopenharmony_ci        if debug:
17876e6818aSopenharmony_ci            TS.start()
17976e6818aSopenharmony_ci
18076e6818aSopenharmony_ci        m2_width = len(im2[0])
18176e6818aSopenharmony_ci        m2_height = len(im2)
18276e6818aSopenharmony_ci        arr_width = len(im1[0]) - m2_width + 1
18376e6818aSopenharmony_ci        arr_height = len(im1) - m2_height + 1
18476e6818aSopenharmony_ci
18576e6818aSopenharmony_ci        denx, deny = 0, 0
18676e6818aSopenharmony_ci        if not density:
18776e6818aSopenharmony_ci            denx, deny = ImgUtils.get_density(m2_width, m2_height)
18876e6818aSopenharmony_ci        else:
18976e6818aSopenharmony_ci            denx, deny = density
19076e6818aSopenharmony_ci        den_yy = int(m2_height >> deny)
19176e6818aSopenharmony_ci        den_xx = int(m2_width >> denx)
19276e6818aSopenharmony_ci
19376e6818aSopenharmony_ci        total = den_yy * den_xx
19476e6818aSopenharmony_ci        if total == 0:
19576e6818aSopenharmony_ci            total = 1
19676e6818aSopenharmony_ci        max_fail_num = (1 - similar) * total
19776e6818aSopenharmony_ci        if debug:
19876e6818aSopenharmony_ci            print("denXX: %i; denYY: %i; total: %i" % (
19976e6818aSopenharmony_ci                den_xx, den_yy, total))
20076e6818aSopenharmony_ci            print("maxFailNum %i" % max_fail_num)
20176e6818aSopenharmony_ci        starttime = time.time()
20276e6818aSopenharmony_ci        endtime = starttime + 5.0 * 60.0
20376e6818aSopenharmony_ci        for arr_h in range(arr_height):
20476e6818aSopenharmony_ci            for arr_w in range(arr_width):
20576e6818aSopenharmony_ci                # 对图片对比设置超时限制
20676e6818aSopenharmony_ci                if time.time() <= endtime:
20776e6818aSopenharmony_ci                    # 1. 对比当前位置的图片是否符合要求
20876e6818aSopenharmony_ci                    fail_num = 0
20976e6818aSopenharmony_ci                    found = True
21076e6818aSopenharmony_ci                    for _yy in range(den_yy):
21176e6818aSopenharmony_ci                        for _xx in range(den_xx):
21276e6818aSopenharmony_ci                            x_den = _xx << denx
21376e6818aSopenharmony_ci                            y_den = _yy << deny
21476e6818aSopenharmony_ci                            m2_val = im2[y_den][x_den]
21576e6818aSopenharmony_ci                            m1_val = im1[arr_h + y_den][x_den + arr_w]
21676e6818aSopenharmony_ci                            if m1_val != m2_val:
21776e6818aSopenharmony_ci                                fail_num += 1
21876e6818aSopenharmony_ci                                if max_fail_num <= fail_num:
21976e6818aSopenharmony_ci                                    found = False
22076e6818aSopenharmony_ci                                    break
22176e6818aSopenharmony_ci                        if not found:
22276e6818aSopenharmony_ci                            break
22376e6818aSopenharmony_ci                    if found:
22476e6818aSopenharmony_ci                        if debug:
22576e6818aSopenharmony_ci                            TS.stop("find_arr")
22676e6818aSopenharmony_ci                        if rect:
22776e6818aSopenharmony_ci                            # @UnusedVariable
22876e6818aSopenharmony_ci                            rect_l, rect_t, rect_r, rect_b = rect
22976e6818aSopenharmony_ci                            return (1 - fail_num / total), (
23076e6818aSopenharmony_ci                                arr_w + rect_l, arr_h + rect_t, arr_w +
23176e6818aSopenharmony_ci                                m2_width + rect_l, arr_h + m2_height + rect_t)
23276e6818aSopenharmony_ci                        return (1 - fail_num / total), (
23376e6818aSopenharmony_ci                            arr_w, arr_h, arr_w + m2_width, arr_h + m2_height)
23476e6818aSopenharmony_ci                else:
23576e6818aSopenharmony_ci                    return None, None
23676e6818aSopenharmony_ci        if debug:
23776e6818aSopenharmony_ci            TS.stop("find_arr")
23876e6818aSopenharmony_ci        return None, None
23976e6818aSopenharmony_ci
24076e6818aSopenharmony_ci    @staticmethod
24176e6818aSopenharmony_ci    def img_filter(filter_img, *filters):
24276e6818aSopenharmony_ci        last_img = filter_img
24376e6818aSopenharmony_ci        for _filter in filters:
24476e6818aSopenharmony_ci            last_img = last_img.filter(_filter)
24576e6818aSopenharmony_ci        return last_img
24676e6818aSopenharmony_ci
24776e6818aSopenharmony_ci    @staticmethod
24876e6818aSopenharmony_ci    def get_density(width, height, maxWNum=4, maxHNum=4):
24976e6818aSopenharmony_ci        denx, deny = 0, 0
25076e6818aSopenharmony_ci        while width > maxWNum:
25176e6818aSopenharmony_ci            denx += 1
25276e6818aSopenharmony_ci            width = int(width >> 1)
25376e6818aSopenharmony_ci        while height > maxHNum:
25476e6818aSopenharmony_ci            deny += 1
25576e6818aSopenharmony_ci            height = int(height >> 1)
25676e6818aSopenharmony_ci        return denx, deny
257