tdf#100451 convert texture buffer to 1-bit and 4-bit palette buffer

OpenGL doesn't support palettes so when the texture is created,
the bitmap buffer is converted to 24-bit RGB. This works nice for
showing the bitmaps on screen. The problem arises when we want to
read the bitmap buffer back (like in a PDF export) as we have to
convert that back to 1-bit or 4-bit palette bitmap buffer. For 4-bit
this was not implemented yet, on the other hand for 1-bit it was
implemented but it didn't take palette into account so the bitmap
was not correct (inverted).

This commit introduces a ScanlineWriter which handles writing
RGB colors to 1-bit and 4-bit palette scanlines. The class sets
up the masks and shifts needed to place the color information
at the correct place in a byte. It also automatically converts a
RGB to palette index.

Change-Id: Ie66ca8cecff40c1252072ba95196ef65ba787f4c
Reviewed-on: https://gerrit.libreoffice.org/26532
Reviewed-by: Tomaž Vajngerl <quikee@gmail.com>
Tested-by: Tomaž Vajngerl <quikee@gmail.com>
diff --git a/vcl/opengl/salbmp.cxx b/vcl/opengl/salbmp.cxx
index fbfcf6f..ef081ee 100644
--- a/vcl/opengl/salbmp.cxx
+++ b/vcl/opengl/salbmp.cxx
@@ -415,7 +415,48 @@ void lclInstantiateTexture(OpenGLTexture& rTexture, const int nWidth, const int 
    rTexture = OpenGLTexture (nWidth, nHeight, nFormat, nType, pData);
}

}
// Write color information for 1 and 4 bit palette bitmap scanlines.
class ScanlineWriter
{
    BitmapPalette& maPalette;
    sal_uInt8 mnColorsPerByte; // number of colors that are stored in one byte
    sal_uInt8 mnColorBitSize;  // number of bits a color takes
    sal_uInt8 mnColorBitMask;  // bit mask used to isolate the color
    sal_uInt8* mpCurrentScanline;
    long mnX;

public:
    ScanlineWriter(BitmapPalette& aPalette, sal_Int8 nColorsPerByte)
        : maPalette(aPalette)
        , mnColorsPerByte(nColorsPerByte)
        , mnColorBitSize(8 / mnColorsPerByte) // bit size is number of bit in a byte divided by number of colors per byte (8 / 2 = 4 for 4-bit)
        , mnColorBitMask((1 << mnColorBitSize) - 1) // calculate the bit mask from the bit size
        , mpCurrentScanline(nullptr)
        , mnX(0)
    {}

    inline void writeRGB(sal_uInt8 nR, sal_uInt8 nG, sal_uInt8 nB)
    {
        // calculate to which index we will write
        long nScanlineIndex = mnX / mnColorsPerByte;

        // calculate the number of shifts to get the color information to the right place
        long nShift = (8 - mnColorBitSize) - ((mnX % mnColorsPerByte) * mnColorBitSize);

        sal_uInt16 nColorIndex = maPalette.GetBestIndex(BitmapColor(nR, nG, nB));
        mpCurrentScanline[nScanlineIndex] &= ~(mnColorBitMask << nShift); // clear
        mpCurrentScanline[nScanlineIndex] |= (nColorIndex & mnColorBitMask) << nShift; // set
        mnX++;
    }

    inline void nextLine(sal_uInt8* pScanline)
    {
        mnX = 0;
        mpCurrentScanline = pScanline;
    }
};

} // end anonymous namespace

Size OpenGLSalBitmap::GetSize() const
{
@@ -567,43 +608,43 @@ bool OpenGLSalBitmap::ReadTexture()
#endif
        return true;
    }
    else if (mnBits == 1)
    {   // convert buffers from 24-bit RGB to 1-bit Mask
    else if (mnBits == 1 || mnBits == 4)
    {   // convert buffers from 24-bit RGB to 1 or 4-bit buffer
        std::vector<sal_uInt8> aBuffer(mnWidth * mnHeight * 3);

        sal_uInt8* pBuffer = aBuffer.data();
        determineTextureFormat(24, nFormat, nType);
        maTexture.Read(nFormat, nType, pBuffer);
        sal_uInt16 nSourceBytesPerRow = lclBytesPerRow(24, mnWidth);

        int nShift = 7;
        size_t nIndex = 0;

        sal_uInt8* pCurrent = pBuffer;
        std::unique_ptr<ScanlineWriter> pWriter;
        switch(mnBits)
        {
            case 1:
                pWriter.reset(new ScanlineWriter(maPalette, 8));
                break;
            case 4:
            default:
                pWriter.reset(new ScanlineWriter(maPalette, 2));
                break;
        }

        for (int y = 0; y < mnHeight; ++y)
        {
            sal_uInt8* pSource = &pBuffer[y * nSourceBytesPerRow];
            sal_uInt8* pDestination = &pData[y * mnBytesPerRow];

            pWriter->nextLine(pDestination);

            for (int x = 0; x < mnWidth; ++x)
            {
                if (nShift < 0)
                {
                    nShift = 7;
                    nIndex++;
                    pData[nIndex] = 0;
                }
                // read source
                sal_uInt8 nR = *pSource++;
                sal_uInt8 nG = *pSource++;
                sal_uInt8 nB = *pSource++;

                sal_uInt8 nR = *pCurrent++;
                sal_uInt8 nG = *pCurrent++;
                sal_uInt8 nB = *pCurrent++;

                if (nR > 0 && nG > 0 && nB > 0)
                {
                    pData[nIndex] |= (1 << nShift);
                }
                nShift--;
                pWriter->writeRGB(nR, nG, nB);
            }
            nShift = 7;
            nIndex++;
            pData[nIndex] = 0;
        }
        return true;
    }
@@ -612,7 +653,6 @@ bool OpenGLSalBitmap::ReadTexture()
             << mnWidth << "x" << mnHeight << "- unimplemented bit depth: "
             << mnBits);
    return false;

}

sal_uInt16 OpenGLSalBitmap::GetBitCount() const