约数,公约数及最大公约数在图片特效中的应用

/*******************************************************************
• 此文章解释权归windsome所有,如要转载无须联系本人。
• 转载需要包含此抬头信息,如有疑问,欢迎联系本人。
• QQ: 35327864
• msn: fgwf1@hotmail.com
• mail: agooou@gmail.com
 ********************************************************************/


    约数又叫因数(在正整数范围内)。整数a能被整数b整除,a叫做b的倍数,b就叫做a的约数。 一个数的约数包括1 及其本身。例如,6的约数有:1、2、3、6,10的约数有:1、2、5、10,15的约数有:1、3、5、15 。
    公约数:如果一个自然数同时是若干个自然数的约数,那么称这个自然数是这若干个自然数的公约数。在所有公约数中最大的一个公约数,称之为这若干个自然数的最大公约数。例如:(8,12)=4,(6,9,15)=3.
    我们在实现一些图片特效中常需要用到约数及公约数,比如:百叶窗,淡入淡出,左进右出,各种栅格效果等。
    百叶窗效果,就是将一张图片纵向切割成N等份,每一块分成M次显示,就像拉上百叶窗一样。在实际图片的处理中,总会遇到不能均有等分的情况,但为了有良好的效果,我们需要显示平滑。举个例子,我们通常在程序中处理此特效的时候要求N及M的值固定,并且要求显示区域宽度L也固定,比如N=10,M=10,L=1000,这样就是将L=1000像素宽的区域分成N=10块进行分别显示,每一块分成M=10部分显示,每次显示每个部分的K=L/N/M=10像素。对于L=1000,这样的显示是平滑的,效果良好的。如果L=990,N=10,M=10,会怎样呢?K=L/N/M=int(9.9)=9,这样会导致每次只画9个像素,整张图将有90个像素没有画,这个效果就很差了。对于这种情况,我们改如何处理呢?
    图片及显示区域总是随机的,这样如果将M,N定死就无法做出良好的效果,所以我们就考虑将M,N的值随着显示区域及图片大小动态改变。如何考虑会达到较为良好的效果?我认为平滑的画完整张图,而不留下漏洞是上佳方案。这样就必须使得N*M能被L整除。意思也就是N是L的约数,M是L/N的约数,我倾向于将N设为一个不大于我预设值的最大约数,M同样。
    方案敲定了,该如何编程实现呢?
    目前实现约数的算法有好多,一般都要用到质数。首先找到质数,然后通过质数去算约数。质数算法我是抄了一位仁兄的,忘了网址在哪。
    接口为:
class UtilMath {
public:
    UtilMath ();

    /**
     * get a biggest divisor, but not bigger than limit.
     *
     * @param num
     * @param limit
     *
     * @return
     */
    static int DivisorLimit (int num, int limit);
    /**
     * get the biggest common divisor of two number.
     *
     * @param numa
     * @param numb
     *
     * @return
     */
    static int CommonDivisorBiggest (int numa, int numb);
    static int CommonDivisorLimit (int numa, int numb, int limit);
};
    实现为:
static int* prime = NULL; // we do not release it after init.
static int prime_count = 0;
// 合数过滤筛选法 Ver2
// 参数:n 求解n以内(包括n)的素数
// 返回值:n以内素数个数
static int InitPrime (int n)
{
    if (prime != NULL)
        return prime_count;
    int i, j;
    // 分配素数标记空间,明白+1原因了吧,因为浪费了一个flag[0]
    char* flag = (char*) malloc (n + 1);

    // 初始化素数标记,要高效点咯
    flag[2] = 1;
    // 注意是i<n不是上例中的i<=n了,理由自思
    for (i = 3; i < n; i++) {
        flag[i++] = 1;
        // 偶数自然不是素数,直接置0好了
        flag[i] = 0;
    }
    // n为奇数
    if (n % 2 != 0) {
        flag[n] = 1;
    }

    // 从3开始filter,因为2的倍数早在初始化时代就干掉了
    // 到n/2止的理由还要说吗
    for (i = 3; i <= n/2; i++) {
        // i是合数,请歇着吧,因为您的工作早有您的质因子代劳了
        if (0 == flag[i])
            continue;
        // 从i的2倍开始过滤,变乘法为加法 
        for (j = i + i; j <= n; j+=i) {
            flag[j] = 0;
        }
    }

    // 统计素数个数
    int count = 0;
    for (i = 2; i <= n; i++) {
        if (flag[i]) count++;
    }
    prime_count = count;
    prime = new int[count];
    for (i = 2, j = 0; i <= n; i++) {
        if (flag[i]) {
            prime[j] = i;
            j++;
        }
    }
    // 释放内存,别忘了传说中的内存泄漏
    free(flag);
    return count;
}

//递归求出一个数的约数集合。
static set<int> getLimitDivisors (int seed, int limit, int value[][MAXCOUNT+2], int index) {
    set<int> result;
    if (value[index][0] == 0) {
        //end of dimension;
        result.insert(seed);
    } else {
        for (int i = 0; i < value[index][0]; i++) {
            //put every set of deminsion.
            int subseed = seed* value[index][1+i];
            if (subseed <= limit) {
                set<int> subresult = getLimitDivisors (subseed, limit, value, index+1);
                set<int>::iterator it = subresult.begin ();
                for (; it != subresult.end (); it++) {
                    result.insert(*it);
                }
            }
        }
    }
    return result;
}

//求出一个数num的最接近limit的约数
int UtilMath::DivisorLimit (int num, int limit) {
    if (num < 4)
        return num;
    InitPrime (10000);

    int result[MAXCOUNT] = {0};
    int tempvalue = num;
    int i = 0, j = 0;
    while (i < MAXCOUNT) {
        if (prime[i] > tempvalue)
            break;
        while (tempvalue % prime[i] == 0) {
            tempvalue = tempvalue / prime[i];
            result[i]++;
        }
        i++;
    }

#if DEBUG_MATH
    printf ("debug: print out result1: /n");
    for (i = 0; i < MAXCOUNT; i++) {
        if (result[i] > 0) {
            printf ("%d[%d] ", prime[i], result[i]);
        }
    }
    printf ("/n");
#endif
    int result2[MAXCOUNT][MAXCOUNT + 2] = {{0}};
    int dimension = 0;
    for (i = 0, j = 0; i < MAXCOUNT; i++) {
        if (result[i] > 0) {
            // store the inner dimension. including pow (prime, 0) = 1.
            result2[j][0] = result[i] + 1;
            for (int tmp_i = 0, k = 1; tmp_i <= result[i]; tmp_i++) {
                int tmp_v = pow (prime[i], tmp_i);
                if (tmp_v <= limit) {
                    result2[j][k] = tmp_v;
                    k++;
                }
            }
            j++;
        }
    }
    dimension = j;

#if DEBUG_MATH
    printf ("debug: print out result2: /n");
    for (i = 0; i < MAXCOUNT; i++) {
        if (result2[i][0] > 0) {
            for (j = 0; j < MAXCOUNT; j++) {
                if (result2[i][j] > 0) {
                    printf ("%d ", result2[i][j]);
                }
            }
            printf ("/n");
        }
    }
    printf ("/n");
#endif
    set<int> values = getLimitDivisors (1, limit, result2, 0);

#if DEBUG_MATH
    printf ("debug: print out result3: /n");
    for (set<int>::iterator it = values.begin (); it != values.end (); it++) {
        printf ("%d ", *it);
    }
    printf ("/n");
#endif
    int best = 1;
    for (set<int>::iterator it = values.begin (); it != values.end (); it++) {
        if (*it > best)
            best = *it;
    }
#if DEBUG_MATH
    printf ("the best divisor is [%d]/n", best);
#endif
    return best;
}

//求2个数的最大公约数
int UtilMath::CommonDivisorBiggest (int numa, int numb) {
    int t;
    if (numa == 0 || numb == 0)
        return 0;
    while ((t = numa % numb) != 0) {
        numa = numb;
        numb = t;
    }
    return numb;
}

//求2个数的公约数中最接近limit的那个
int UtilMath::CommonDivisorLimit (int numa, int numb, int limit) {
    int biggest = CommonDivisorBiggest (numa, numb);

    return DivisorLimit (biggest, limit);
}

至于其他的图片特效,算法都大同小异,都是使用这些基本算法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值