Shell's Home

Dec 22, 2006 - 3 minute read - Comments

EPS转BMP和代码优化

EPS转BMP(我用的是PNG,不过还是一回事情)。EPS内部有两种图片,一个是TIF,位置和大小在头32个字节中描述。具体可以看EPS文档,在lp[5]保存位置,lp[6]保存大小。还有就是%%BeginBinary:

后面跟一个大小,然后跟beginimagex0D。在前文中应当有大小,自己找找看去,最后是一堆数据。存放方法是一位一位的像素连续存放,先存放一行C,然后是一行M,然后是Y,然后是K。当这4者全部存放完后,向后跳空一端区域,到本行开始的4字节对齐处,开始下一行的存放。

例如3个像素宽的图片(好简单——)存放方法就是,在0字节的最高三位(7,6,5位),存放了这三个像素的C值。次三位(4,3,2位),存放了M值。然后Y值存放在0字节的两位(1,0位)和1字节的一位(7位)。最后K值存放在1字节的次三位(6,5,4位)。然后跳空对齐,在4字节的位置开始描述下一行。

了解这个过程了,就应该发现,要转换到BMP需要大量的位操作。前置过程不说了,假定数据在内存里面(我当然不会用读取这种方法,用的是文件内存映射啦),然后目标是GDI+的BitmapData对象。

BYTE getBit (PBYTE lpInData, int bit)
{
    bit*=n;
    BYTE rslt;
    rslt = lpInData[bit / 8];
    rslt >>= (7 - bit % 8);
    return rslt & 0x1;
}

for (y = 0; y < tgtData.Height; y++) {
    line_start = lpOut + l * n * Stride;
    for (x = 0; x < tgtData.Width; x++) {
        c = 1 - getBit (line_start, x);
        m = 1 - getBit (line_start, x + tgtData.Width);
        yy = 1 - getBit (line_start, x + 2 * tgtData.Width);
        k = getBit (line_start, x + 3 * tgtData.Width);
        r = 1 - min (1, c * (1 - k) + k);
        g = 1 - min (1, m * (1 - k) + k);
        b = 1 - min (1, yy * (1 - k) + k);
        if (x % 2) {
            *((PBYTE) tgtData.Scan0 + l * tgtData.Stride + x / 2) |=
                (g << 6) + (r << 5) + (b << 4);
        } else {
            *((PBYTE) tgtData.Scan0 + l * tgtData.Stride + x / 2) |=
                (m << 2) + (c << 1) + yy;
        }
    }
}

贴上来的代码格式都不怎么样,拿indent格一下就好了。

原本来说,CMYK转RGM是有一个算法的。在这里,CMKY都是非1即0。我们可以运算出,当K等于1的时候,结果就是RGB=0;所以当K为1的时候无需进行取值运算写入操作。这是第一步优化。当K等于0的时候,RGB等于CMY求反。所以无需多三个变量,不用1-运算就可以了,这是第二步优化。

for (y = 0; y < tgtData.Height; y++) {
    line_start = lpOut + l * n * Stride;
    for (x = 0; x < tgtData.Width; x++) {
        k = getBit (line_start, x + 3 * tgtData.Width);
        if(k){
            c =getBit (line_start, x);
            m =getBit (line_start, x + tgtData.Width);
            yy =getBit (line_start, x + 2 * tgtData.Width);
            if (x % 2) {
                *((PBYTE) tgtData.Scan0 + l * tgtData.Stride + x / 2) |=
                    (c << 6) + (m << 5) + (yy << 4);
            } else {
                *((PBYTE) tgtData.Scan0 + l * tgtData.Stride + x / 2) |=
                    (c << 2) + (m << 1) + yy;
            }
        }
    }
}

然后是获得CMKY的过程。我原先是使用一个函数来获得的,但是函数获得的时候会出现一个问题。在循环中使用函数会打断循环的流水,造成效率下降,因此改成内嵌。内联函数(inline faction)理论上可以,但是有两个问题。一个是有的编译器理解内联为建议关键字,是否执行要看编译器的心情。还有一个是使用内联就无法使用register关键字来优化,虽然这个也是建议关键字。内嵌写起来很长,因此使用宏来解决。这是第三步优化。

再然后是减少循环中计算的规模,降低计算难度。这是细节优化,本质上是空间换时间,不过空间消耗很小而时间加速明显。首先是将Width1和Width3计算出来备用,减少核心循环中乘法次数(双循环内减少了三次乘法)。将pic_line_start的计算从内层循环提到外层(单循环减少内循环数目减一次乘法和加法。乘法是主要目的,加法可以到内核循环中做,不怎么费时,不过没必要在能优化的地方放过,还弄的程序更复杂就是了)。bit计算的时候本来是x*n+分别的Width1-Width3,可以改成减Width1(减少内核两次乘法)。这是第四步优化。

最后是细节,例如bit/8改成bit>>3,bit%8改成bit&0x07,x%2改成x&0x01。尽量使用使用+= -= >>=操作,减少运算目数。这是第五步优化。

最后结果

#define GETBIT(byte) byte = line_start[bit >> 3];byte >>= ~bit & 0x07;byte &= 0x01;

int eps2gdip::DataProcess (BitmapData & tgtData)
{
    BYTE c, m, y, k;
    UINT x, l, Width1, Width3;
    register ULONG bit;
    BYTE RGB;
    PBYTE line_start, pic_line_start;
    Width1 = n * tgtData.Width;
    Width3 = n * tgtData.Width * 3;
    for (l = 0; l < tgtData.Height; l++) {
        line_start = lpOut + l * n * Stride;
        pic_line_start = (PBYTE) tgtData.Scan0 + l * tgtData.Stride;
        for (x = 0; x < tgtData.Width; x++) {
            bit = Width3 + x * n;
            GETBIT (k);
            if (k) {
                bit -= Width3;
                GETBIT (c);
                bit += Width1;
                GETBIT (m);
                bit += Width1;
                GETBIT (y);
                RGB = (c << 2) | (m << 1) | (y);
                if (x & 0x01)
                    *(pic_line_start + x / 2) |= (RGB << 4);
                else
                    *(pic_line_start + x / 2) |= RGB;
            }
        }
    }
    return 0;
}

结果测试,在100M文件的完全运算中,处理时间从83秒降低到35秒。其中结果保存等动作耗费20秒,即核心运算从63秒降低到15秒。效率增加了300%。