/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */
 
#include <sal/config.h>
 
#include <basegfx/numeric/ftools.hxx>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/rendering/XIntegerBitmap.hpp>
#include <tools/diagnose_ex.h>
#include <vcl/bitmap.hxx>
#include <vcl/bitmapex.hxx>
#include <vcl/bitmapaccess.hxx>
#include <vcl/canvastools.hxx>
 
#include "dx_impltools.hxx"
#include "dx_vcltools.hxx"
 
using namespace ::com::sun::star;
 
namespace dxcanvas
{
    namespace tools
    {
        namespace
        {
            /// Calc number of colors in given BitmapInfoHeader
            sal_Int32 calcDIBColorCount( const BITMAPINFOHEADER& rBIH )
            {
                if( rBIH.biSize != sizeof( BITMAPCOREHEADER ) )
                {
                    if( rBIH.biBitCount <= 8 )
                    {
                        if( rBIH.biClrUsed )
                            return rBIH.biClrUsed;
                        else
                            return 1 << rBIH.biBitCount;
                    }
                }
                else
                {
                    BITMAPCOREHEADER const * pCoreHeader = reinterpret_cast<BITMAPCOREHEADER const *>(&rBIH);
 
                    if( pCoreHeader->bcBitCount <= 8 )
                        return 1 << pCoreHeader->bcBitCount;
                }
 
                return 0; // nothing known
            }
 
            /// Draw DI bits to given Graphics
            bool drawDIBits( const std::shared_ptr< Gdiplus::Graphics >& rGraphics,
                             const void*                                     hDIB )
            {
                bool            bRet( false );
                BitmapSharedPtr pBitmap;
 
                const BITMAPINFO* pBI = static_cast<BITMAPINFO*>(GlobalLock( const_cast<void *>(hDIB) ));
 
                if( pBI )
                {
                    const BYTE*             pBits = reinterpret_cast<BYTE const *>(pBI) + pBI->bmiHeader.biSize +
                        calcDIBColorCount( pBI->bmiHeader ) * sizeof( RGBQUAD );
 
                    // forward to outsourced GDI+ rendering method
                    // (header clashes)
                    bRet = tools::drawDIBits( rGraphics, *pBI, pBits );
 
                    GlobalUnlock( const_cast<void *>(hDIB) );
                }
 
                return bRet;
            }
 
            /** Draw VCL bitmap to given Graphics
 
                @param rBmp
                Reference to bitmap. Might get modified, in such a way
                that it will hold a DIB after a successful function call.
             */
            bool drawVCLBitmap( const std::shared_ptr< Gdiplus::Graphics >& rGraphics,
                                ::Bitmap&                                       rBmp )
            {
                BitmapSystemData aBmpSysData;
 
                if( !rBmp.GetSystemData( aBmpSysData ) ||
                    !aBmpSysData.pDIB )
                {
                    // first of all, ensure that Bitmap contains a DIB, by
                    // acquiring a read access
                    BitmapReadAccess* pReadAcc = rBmp.AcquireReadAccess();
 
                    // TODO(P2): Acquiring a read access can actually
                    // force a read from VRAM, thus, avoiding this
                    // step somehow will increase performance
                    // here.
                    if( pReadAcc )
                    {
                        // try again: now, WinSalBitmap must have
                        // generated a DIB
                        if( rBmp.GetSystemData( aBmpSysData ) &&
                            aBmpSysData.pDIB )
                        {
                            return drawDIBits( rGraphics,
                                               aBmpSysData.pDIB );
                        }
 
                        Bitmap::ReleaseAccess( pReadAcc );
                    }
                }
                else
                {
                    return drawDIBits( rGraphics,
                                       aBmpSysData.pDIB );
                }
 
                // failed to generate DIBits from vcl bitmap
                return false;
            }
 
            /** Create a chunk of raw RGBA data GDI+ Bitmap from VCL BbitmapEX
             */
            RawRGBABitmap bitmapFromVCLBitmapEx( const ::BitmapEx& rBmpEx )
            {
                // TODO(P2): Avoid temporary bitmap generation, maybe
                // even ensure that created DIBs are copied back to
                // BmpEx (currently, every AcquireReadAccess() will
                // make the local bitmap copy unique, effectively
                // duplicating the memory used)
 
                ENSURE_OR_THROW( rBmpEx.IsTransparent(),
                                  "::dxcanvas::tools::bitmapFromVCLBitmapEx(): "
                                  "BmpEx not transparent" );
 
                // convert transparent bitmap to 32bit RGBA
                // ========================================
 
                const ::Size aBmpSize( rBmpEx.GetSizePixel() );
 
                RawRGBABitmap aBmpData;
                aBmpData.mnWidth     = aBmpSize.Width();
                aBmpData.mnHeight    = aBmpSize.Height();
                aBmpData.mpBitmapData.reset( new sal_uInt8[ 4*aBmpData.mnWidth*aBmpData.mnHeight ] );
 
                Bitmap aBitmap( rBmpEx.GetBitmap() );
 
                Bitmap::ScopedReadAccess pReadAccess( aBitmap );
 
                const sal_Int32 nWidth( aBmpSize.Width() );
                const sal_Int32 nHeight( aBmpSize.Height() );
 
                ENSURE_OR_THROW( pReadAccess.get() != nullptr,
                                  "::dxcanvas::tools::bitmapFromVCLBitmapEx(): "
                                  "Unable to acquire read access to bitmap" );
 
                if( rBmpEx.IsAlpha() )
                {
                    Bitmap aAlpha( rBmpEx.GetAlpha().GetBitmap() );
 
                    Bitmap::ScopedReadAccess pAlphaReadAccess( aAlpha );
 
                    // By convention, the access buffer always has
                    // one of the following formats:
 
                    //    ScanlineFormat::N1BitMsbPal
                    //    ScanlineFormat::N4BitMsnPal
                    //    ScanlineFormat::N8BitPal
                    //    ScanlineFormat::N16BitTcLsbMask
                    //    ScanlineFormat::N24BitTcBgr
                    //    ScanlineFormat::N32BitTcMask
 
                    // and is always ScanlineFormat::BottomUp
 
                    // This is the way
                    // WinSalBitmap::AcquireBuffer() sets up the
                    // buffer
 
                    ENSURE_OR_THROW( pAlphaReadAccess.get() != nullptr,
                                      "::dxcanvas::tools::bitmapFromVCLBitmapEx(): "
                                      "Unable to acquire read access to alpha" );
 
                    ENSURE_OR_THROW( pAlphaReadAccess->GetScanlineFormat() == ScanlineFormat::N8BitPal ||
                                      pAlphaReadAccess->GetScanlineFormat() == ScanlineFormat::N8BitTcMask,
                                      "::dxcanvas::tools::bitmapFromVCLBitmapEx(): "
                                      "Unsupported alpha scanline format" );
 
                    BitmapColor     aCol;
                    sal_uInt8*      pCurrOutput( aBmpData.mpBitmapData.get() );
                    int             x, y;
 
                    for( y=0; y<nHeight; ++y )
                    {
                        switch( pReadAccess->GetScanlineFormat() )
                        {
                            case ScanlineFormat::N8BitPal:
                            {
                                Scanline pScan  = pReadAccess->GetScanline( y );
                                Scanline pAScan = pAlphaReadAccess->GetScanline( y );
 
                                for( x=0; x<nWidth; ++x )
                                {
                                    aCol = pReadAccess->GetPaletteColor( *pScan++ );
 
                                    *pCurrOutput++ = aCol.GetBlue();
                                    *pCurrOutput++ = aCol.GetGreen();
                                    *pCurrOutput++ = aCol.GetRed();
 
                                    // out notion of alpha is
                                    // different from the rest
                                    // of the world's
                                    *pCurrOutput++ = 255 - static_cast<BYTE>(*pAScan++);
                                }
                            }
                            break;
 
                            case ScanlineFormat::N24BitTcBgr:
                            {
                                Scanline pScan  = pReadAccess->GetScanline( y );
                                Scanline pAScan = pAlphaReadAccess->GetScanline( y );
 
                                for( x=0; x<nWidth; ++x )
                                {
                                    // store as RGBA
                                    *pCurrOutput++ = *pScan++;
                                    *pCurrOutput++ = *pScan++;
                                    *pCurrOutput++ = *pScan++;
 
                                    // out notion of alpha is
                                    // different from the rest
                                    // of the world's
                                    *pCurrOutput++ = 255 - static_cast<BYTE>(*pAScan++);
                                }
                            }
                            break;
 
                            // TODO(P2): Might be advantageous
                            // to hand-formulate the following
                            // formats, too.
                            case ScanlineFormat::N1BitMsbPal:
                                // FALLTHROUGH intended
                            case ScanlineFormat::N4BitMsnPal:
                                // FALLTHROUGH intended
                            case ScanlineFormat::N16BitTcLsbMask:
                                // FALLTHROUGH intended
                            case ScanlineFormat::N32BitTcMask:
                            {
                                Scanline pAScan = pAlphaReadAccess->GetScanline( y );
 
                                // using fallback for those
                                // seldom formats
                                for( x=0; x<nWidth; ++x )
                                {
                                    // yes. x and y are swapped on Get/SetPixel
                                    aCol = pReadAccess->GetColor(y,x);
 
                                    *pCurrOutput++ = aCol.GetBlue();
                                    *pCurrOutput++ = aCol.GetGreen();
                                    *pCurrOutput++ = aCol.GetRed();
 
                                    // out notion of alpha is
                                    // different from the rest
                                    // of the world's
                                    *pCurrOutput++ = 255 - static_cast<BYTE>(*pAScan++);
                                }
                            }
                            break;
 
                            case ScanlineFormat::N1BitLsbPal:
                                // FALLTHROUGH intended
                            case ScanlineFormat::N4BitLsnPal:
                                // FALLTHROUGH intended
                            case ScanlineFormat::N8BitTcMask:
                                // FALLTHROUGH intended
                            case ScanlineFormat::N24BitTcRgb:
                                // FALLTHROUGH intended
                            case ScanlineFormat::N16BitTcMsbMask:
                                // FALLTHROUGH intended
                            case ScanlineFormat::N32BitTcAbgr:
                                // FALLTHROUGH intended
                            case ScanlineFormat::N32BitTcArgb:
                                // FALLTHROUGH intended
                            case ScanlineFormat::N32BitTcBgra:
                                // FALLTHROUGH intended
                            case ScanlineFormat::N32BitTcRgba:
                                // FALLTHROUGH intended
                            default:
                                ENSURE_OR_THROW( false,
                                                  "::dxcanvas::tools::bitmapFromVCLBitmapEx(): "
                                                  "Unexpected scanline format - has "
                                                  "WinSalBitmap::AcquireBuffer() changed?" );
                        }
                    }
                }
                else
                {
                    Bitmap aMask( rBmpEx.GetMask() );
 
                    Bitmap::ScopedReadAccess pMaskReadAccess( aMask );
 
                    // By convention, the access buffer always has
                    // one of the following formats:
 
                    //    ScanlineFormat::N1BitMsbPal
                    //    ScanlineFormat::N4BitMsnPal
                    //    ScanlineFormat::N8BitPal
                    //    ScanlineFormat::N16BitTcLsbMask
                    //    ScanlineFormat::N24BitTcBgr
                    //    ScanlineFormat::N32BitTcMask
 
                    // and is always ScanlineFormat::BottomUp
 
                    // This is the way
                    // WinSalBitmap::AcquireBuffer() sets up the
                    // buffer
 
                    ENSURE_OR_THROW( pMaskReadAccess.get() != nullptr,
                                      "::dxcanvas::tools::bitmapFromVCLBitmapEx(): "
                                      "Unable to acquire read access to mask" );
 
                    ENSURE_OR_THROW( pMaskReadAccess->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal,
                                      "::dxcanvas::tools::bitmapFromVCLBitmapEx(): "
                                      "Unsupported mask scanline format" );
 
                    BitmapColor     aCol;
                    int             nCurrBit;
                    const int       nMask( 1 );
                    const int       nInitialBit(7);
                    sal_uInt8*      pCurrOutput( aBmpData.mpBitmapData.get() );
                    int             x, y;
 
                    // mapping table, to get from mask index color to
                    // alpha value (which depends on the mask's palette)
                    sal_uInt8 aColorMap[2];
 
                    const BitmapColor& rCol0( pMaskReadAccess->GetPaletteColor( 0 ) );
                    const BitmapColor& rCol1( pMaskReadAccess->GetPaletteColor( 1 ) );
 
                    // shortcut for true luminance calculation
                    // (assumes that palette is grey-level). Note the
                    // swapped the indices here, to account for the
                    // fact that VCL's notion of alpha is inverted to
                    // the rest of the world's.
                    aColorMap[0] = rCol1.GetRed();
                    aColorMap[1] = rCol0.GetRed();
 
                    for( y=0; y<nHeight; ++y )
                    {
                        switch( pReadAccess->GetScanlineFormat() )
                        {
                            case ScanlineFormat::N8BitPal:
                            {
                                Scanline pScan  = pReadAccess->GetScanline( y );
                                Scanline pMScan = pMaskReadAccess->GetScanline( y );
 
                                for( x=0, nCurrBit=nInitialBit; x<nWidth; ++x )
                                {
                                    aCol = pReadAccess->GetPaletteColor( *pScan++ );
 
                                    *pCurrOutput++ = aCol.GetBlue();
                                    *pCurrOutput++ = aCol.GetGreen();
                                    *pCurrOutput++ = aCol.GetRed();
 
                                    *pCurrOutput++ = aColorMap[ (pMScan[ (x & ~7) >> 3 ] >> nCurrBit ) & nMask ];
                                    nCurrBit = ((nCurrBit - 1) % 8) & 7;
                                }
                            }
                            break;
 
                            case ScanlineFormat::N24BitTcBgr:
                            {
                                Scanline pScan  = pReadAccess->GetScanline( y );
                                Scanline pMScan = pMaskReadAccess->GetScanline( y );
 
                                for( x=0, nCurrBit=nInitialBit; x<nWidth; ++x )
                                {
                                    // store as RGBA
                                    *pCurrOutput++ = *pScan++;
                                    *pCurrOutput++ = *pScan++;
                                    *pCurrOutput++ = *pScan++;
 
                                    *pCurrOutput++ = aColorMap[ (pMScan[ (x & ~7) >> 3 ] >> nCurrBit ) & nMask ];
                                    nCurrBit = ((nCurrBit - 1) % 8) & 7;
                                }
                            }
                            break;
 
                            // TODO(P2): Might be advantageous
                            // to hand-formulate the following
                            // formats, too.
                            case ScanlineFormat::N1BitMsbPal:
                                // FALLTHROUGH intended
                            case ScanlineFormat::N4BitMsnPal:
                                // FALLTHROUGH intended
                            case ScanlineFormat::N16BitTcLsbMask:
                                // FALLTHROUGH intended
                            case ScanlineFormat::N32BitTcMask:
                            {
                                Scanline pMScan = pMaskReadAccess->GetScanline( y );
 
                                // using fallback for those
                                // seldom formats
                                for( x=0, nCurrBit=nInitialBit; x<nWidth; ++x )
                                {
                                    // yes. x and y are swapped on Get/SetPixel
                                    aCol = pReadAccess->GetColor(y,x);
 
                                    // store as RGBA
                                    *pCurrOutput++ = aCol.GetBlue();
                                    *pCurrOutput++ = aCol.GetGreen();
                                    *pCurrOutput++ = aCol.GetRed();
 
                                    *pCurrOutput++ = aColorMap[ (pMScan[ (x & ~7) >> 3 ] >> nCurrBit ) & nMask ];
                                    nCurrBit = ((nCurrBit - 1) % 8) & 7;
                                }
                            }
                            break;
 
                            case ScanlineFormat::N1BitLsbPal:
                                // FALLTHROUGH intended
                            case ScanlineFormat::N4BitLsnPal:
                                // FALLTHROUGH intended
                            case ScanlineFormat::N8BitTcMask:
                                // FALLTHROUGH intended
                            case ScanlineFormat::N24BitTcRgb:
                                // FALLTHROUGH intended
                            case ScanlineFormat::N16BitTcMsbMask:
                                // FALLTHROUGH intended
                            case ScanlineFormat::N32BitTcAbgr:
                                // FALLTHROUGH intended
                            case ScanlineFormat::N32BitTcArgb:
                                // FALLTHROUGH intended
                            case ScanlineFormat::N32BitTcBgra:
                                // FALLTHROUGH intended
                            case ScanlineFormat::N32BitTcRgba:
                                // FALLTHROUGH intended
                            default:
                                ENSURE_OR_THROW( false,
                                                  "::dxcanvas::tools::bitmapFromVCLBitmapEx(): "
                                                  "Unexpected scanline format - has "
                                                  "WinSalBitmap::AcquireBuffer() changed?" );
                        }
                    }
                }
 
                return aBmpData;
            }
 
            bool drawVCLBitmapEx( const std::shared_ptr< Gdiplus::Graphics >& rGraphics,
                                  const ::BitmapEx&                               rBmpEx )
            {
                if( !rBmpEx.IsTransparent() )
                {
                    Bitmap aBmp( rBmpEx.GetBitmap() );
                    return drawVCLBitmap( rGraphics, aBmp );
                }
                else
                {
                    return drawRGBABits( rGraphics,
                                         bitmapFromVCLBitmapEx( rBmpEx ) );
                }
            }
        }
 
        bool drawVCLBitmapFromXBitmap( const std::shared_ptr< Gdiplus::Graphics >& rGraphics,
                                       const uno::Reference< rendering::XBitmap >&     xBitmap )
        {
            // TODO(F2): add support for floating point bitmap formats
            uno::Reference< rendering::XIntegerReadOnlyBitmap > xIntBmp(
                xBitmap, uno::UNO_QUERY );
 
            if( !xIntBmp.is() )
                return false;
 
            ::BitmapEx aBmpEx = vcl::unotools::bitmapExFromXBitmap( xIntBmp );
            if( !aBmpEx )
                return false;
 
            return drawVCLBitmapEx( rGraphics, aBmpEx );
        }
    }
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V554 Incorrect use of shared_ptr. The memory allocated with 'new []' will be cleaned using 'delete'.

V641 The size of the '& rBIH' buffer is not a multiple of the element size of the type 'BITMAPCOREHEADER const'.