views:

100

answers:

1

I've been using the FJCore library in a Silverlight project to help with some realtime image processing, and I'm trying to figure out how to get a tad more compression and performance out of the library. Now, as I understand it, the JPEG standard allows you to specify a chroma subsampling ratio (see http://en.wikipedia.org/wiki/Chroma_subsampling and http://en.wikipedia.org/wiki/Jpeg); and it appears that this is supposed to be implemented in the FJCore library using the HsampFactor and VsampFactor arrays:

    public static readonly byte[] HsampFactor = { 1, 1, 1 };
    public static readonly byte[] VsampFactor = { 1, 1, 1 };

However, I'm having a hard time figuring out how to use them. It looks to me like the current values are supposed to represent 4:4:4 subsampling (e.g., no subsampling at all), and that if I wanted to get 4:1:1 subsampling, the right values would be something like this:

    public static readonly byte[] HsampFactor = { 2, 1, 1 };
    public static readonly byte[] VsampFactor = { 2, 1, 1 };

At least, that's the way that other similar libraries use these values (for instance, see the example code here for libjpeg).

However, neither the above values of {2, 1, 1} nor any other set of values that I've tried besides {1, 1, 1} produce a legible image. Nor, in looking at the code, does it seem like that's the way it's written. But for the life of me, I can't figure out what the FJCore code is actually trying to do. It seems like it's just using the sample factors to repeat operations that it's already done -- i.e., if I didn't know better, I'd say that it was a bug. But this is a fairly established library, based on some fairly well established Java code, so I'd be surprised if that were the case.

Does anybody have any suggestions for how to use these values to get 4:2:2 or 4:1:1 chroma subsampling?

For what it's worth, here's the relevant code from the JpegEncoder class:

for (comp = 0; comp < _input.Image.ComponentCount; comp++)
{
    Width = _input.BlockWidth[comp];
    Height = _input.BlockHeight[comp];

    inputArray = _input.Image.Raster[comp];

    for (i = 0; i < _input.VsampFactor[comp]; i++)
    {
        for (j = 0; j < _input.HsampFactor[comp]; j++)
        {
            xblockoffset = j * 8;
            yblockoffset = i * 8;
            for (a = 0; a < 8; a++)
            {
                // set Y value.  check bounds
                int y = ypos + yblockoffset + a; if (y >= _height) break;

                for (b = 0; b < 8; b++)
                {
                    int x = xpos + xblockoffset + b; if (x >= _width) break;
                    dctArray1[a, b] = inputArray[x, y];
                }
            }
            dctArray2 = _dct.FastFDCT(dctArray1);
            dctArray3 = _dct.QuantizeBlock(dctArray2, FrameDefaults.QtableNumber[comp]);
            _huf.HuffmanBlockEncoder(buffer, dctArray3, lastDCvalue[comp], FrameDefaults.DCtableNumber[comp], FrameDefaults.ACtableNumber[comp]);
            lastDCvalue[comp] = dctArray3[0];
        }
    }
}

And notice that in the i & j loops, they're not controlling any kind of pixel skipping: if HsampFactor[0] is set to two, it's just grabbing two blocks instead of one.

A: 

I figured it out. I thought that by setting the sampling factors, you were telling the library to subsample the raster components itself. Turns out that when you set the sampling factors, you're actually telling the library the relative size of the raster components that you're providing. In other words, you need to do the chroma subsampling of the image yourself, before you ever submit it to the FJCore library for compression. Something like this is what it's looking for:

    private byte[][,] GetSubsampledRaster()
    {
        byte[][,] raster = new byte[3][,];
        raster[Y] = new byte[width / hSampleFactor[Y], height / vSampleFactor[Y]];
        raster[Cb] = new byte[width / hSampleFactor[Cb], height / vSampleFactor[Cb]];
        raster[Cr] = new byte[width / hSampleFactor[Cr], height / vSampleFactor[Cr]];

        int rgbaPos = 0;
        for (short y = 0; y < height; y++)
        {
            int Yy = y / vSampleFactor[Y];
            int Cby = y / vSampleFactor[Cb];
            int Cry = y / vSampleFactor[Cr];
            int Yx = 0, Cbx = 0, Crx = 0;
            for (short x = 0; x < width; x++)
            {
                // Convert to YCbCr colorspace.
                byte b = RgbaSample[rgbaPos++];
                byte g = RgbaSample[rgbaPos++];
                byte r = RgbaSample[rgbaPos++];
                YCbCr.fromRGB(ref r, ref g, ref b);

                // Only include the byte in question in the raster if it matches the appropriate sampling factor.
                if (IncludeInSample(Y, x, y))
                {
                    raster[Y][Yx++, Yy] = r;
                }
                if (IncludeInSample(Cb, x, y))
                {
                    raster[Cb][Cbx++, Cby] = g;
                }
                if (IncludeInSample(Cr, x, y))
                {
                    raster[Cr][Crx++, Cry] = b;
                }

                // For YCbCr, we ignore the Alpha byte of the RGBA byte structure, so advance beyond it.
                rgbaPos++;
            }
        }
        return raster;
    }

    static private bool IncludeInSample(int slice, short x, short y)
    {
        // Hopefully this gets inlined . . . 
        return ((x % hSampleFactor[slice]) == 0) && ((y % vSampleFactor[slice]) == 0);
    }

There might be additional ways to optimize this, but it's working for now.

Ken Smith
@Ken: How is the performance of FJCore compared to the built-in jpeg encoder used by GDI+? I have a need to keep the subsampling at 4:4:4, to allow increased compression without loss of detail (text alongside images). I was wondering if FJCore might do the trick...
Oskar Austegard
I haven't actually tested the two side-by-side: I've been using FJCore not because it was faster or more flexible, but because I needed to use it with Silverlight for motion jpeg video compression. I'd be interested in hearing your results.
Ken Smith