diff options
Diffstat (limited to 'vcl/source/filter/ixpm/xpmread.cxx')
-rw-r--r-- | vcl/source/filter/ixpm/xpmread.cxx | 697 |
1 files changed, 697 insertions, 0 deletions
diff --git a/vcl/source/filter/ixpm/xpmread.cxx b/vcl/source/filter/ixpm/xpmread.cxx new file mode 100644 index 000000000..2a979d2df --- /dev/null +++ b/vcl/source/filter/ixpm/xpmread.cxx @@ -0,0 +1,697 @@ +/* -*- 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 <filter/XpmReader.hxx> + +#include <vcl/graph.hxx> +#include <tools/stream.hxx> + +#include <bitmap/BitmapWriteAccess.hxx> +#include <graphic/GraphicReader.hxx> + +#include "rgbtable.hxx" + +#include <cstring> +#include <array> +#include <map> + +#define XPMTEMPBUFSIZE 0x00008000 +#define XPMSTRINGBUF 0x00008000 + +#define XPMIDENTIFIER 0x00000001 // mnIdentifier includes one of the six phases +#define XPMDEFINITION 0x00000002 // the XPM format consists of +#define XPMVALUES 0x00000003 +#define XPMCOLORS 0x00000004 +#define XPMPIXELS 0x00000005 +#define XPMEXTENSIONS 0x00000006 +#define XPMENDEXT 0x00000007 + +#define XPMREMARK 0x00000001 // defines used by mnStatus +#define XPMDOUBLE 0x00000002 +#define XPMSTRING 0x00000004 +#define XPMFINISHED 0x00000008 + +namespace { + +enum ReadState +{ + XPMREAD_OK, + XPMREAD_ERROR, + XPMREAD_NEED_MORE +}; + +} + +class BitmapWriteAccess; +class Graphic; + +namespace { + +class XPMReader : public GraphicReader +{ +private: + + SvStream& mrIStm; + Bitmap maBmp; + BitmapScopedWriteAccess mpAcc; + Bitmap maMaskBmp; + BitmapScopedWriteAccess mpMaskAcc; + tools::Long mnLastPos; + + sal_uLong mnWidth; + sal_uLong mnHeight; + sal_uLong mnColors; + sal_uInt32 mnCpp; // characters per pix + bool mbTransparent; + bool mbStatus; + sal_uLong mnStatus; + sal_uLong mnIdentifier; + sal_uInt8 mcThisByte; + sal_uInt8 mcLastByte; + sal_uLong mnTempAvail; + sal_uInt8* mpTempBuf; + sal_uInt8* mpTempPtr; + // each key is ( mnCpp )Byte(s)-> ASCII entry assigned to the colour + // each colordata is + // 1 Byte -> 0xFF if colour is transparent + // 3 Bytes -> RGB value of the colour + typedef std::array<sal_uInt8,4> colordata; + typedef std::map<OString, colordata> colormap; + colormap maColMap; + sal_uLong mnStringSize; + sal_uInt8* mpStringBuf; + sal_uLong mnParaSize; + sal_uInt8* mpPara; + + bool ImplGetString(); + bool ImplGetColor(); + bool ImplGetScanLine( sal_uLong ); + bool ImplGetColSub(colordata &rDest); + bool ImplGetColKey( sal_uInt8 ); + void ImplGetRGBHex(colordata &rDest, sal_uLong); + bool ImplGetPara( sal_uLong numb ); + static bool ImplCompare(sal_uInt8 const *, sal_uInt8 const *, sal_uLong); + sal_uLong ImplGetULONG( sal_uLong nPara ); + +public: + explicit XPMReader( SvStream& rStm ); + + ReadState ReadXPM( Graphic& rGraphic ); +}; + +} + +XPMReader::XPMReader(SvStream& rStm) + : mrIStm(rStm) + , mnLastPos(rStm.Tell()) + , mnWidth(0) + , mnHeight(0) + , mnColors(0) + , mnCpp(0) + , mbTransparent(false) + , mbStatus(true) + , mnStatus( 0 ) + , mnIdentifier(XPMIDENTIFIER) + , mcThisByte(0) + , mcLastByte(0) + , mnTempAvail(0) + , mpTempBuf(nullptr) + , mpTempPtr(nullptr) + , mnStringSize(0) + , mpStringBuf(nullptr) + , mnParaSize(0) + , mpPara(nullptr) +{ +} + +ReadState XPMReader::ReadXPM( Graphic& rGraphic ) +{ + ReadState eReadState; + sal_uInt8 cDummy; + + // check if we can real ALL + mrIStm.Seek( STREAM_SEEK_TO_END ); + mrIStm.ReadUChar( cDummy ); + + // if we could not read all + // return and wait for new data + if ( mrIStm.GetError() != ERRCODE_IO_PENDING ) + { + mrIStm.Seek( mnLastPos ); + mbStatus = true; + + if ( mbStatus ) + { + mpStringBuf = new sal_uInt8 [ XPMSTRINGBUF ]; + mpTempBuf = new sal_uInt8 [ XPMTEMPBUFSIZE ]; + + mbStatus = ImplGetString(); + if ( mbStatus ) + { + mnIdentifier = XPMVALUES; // fetch Bitmap information + mnWidth = ImplGetULONG( 0 ); + mnHeight = ImplGetULONG( 1 ); + mnColors = ImplGetULONG( 2 ); + mnCpp = ImplGetULONG( 3 ); + } + if ( mnColors > ( SAL_MAX_UINT32 / ( 4 + mnCpp ) ) ) + mbStatus = false; + if ( ( mnWidth * mnCpp ) >= XPMSTRINGBUF ) + mbStatus = false; + //xpms are a minimum of one character (one byte) per pixel, so if the file isn't + //even that long, it's not all there + if (mrIStm.remainingSize() + mnTempAvail < static_cast<sal_uInt64>(mnWidth) * mnHeight) + mbStatus = false; + if ( mbStatus && mnWidth && mnHeight && mnColors && mnCpp ) + { + mnIdentifier = XPMCOLORS; + + for (sal_uLong i = 0; i < mnColors; ++i) + { + if (!ImplGetColor()) + { + mbStatus = false; + break; + } + } + + if ( mbStatus ) + { + // create a 24bit graphic when more as 256 colours present + auto ePixelFormat = vcl::PixelFormat::INVALID; + if ( mnColors > 256 ) + ePixelFormat = vcl::PixelFormat::N24_BPP; + else if ( mnColors > 2 ) + ePixelFormat = vcl::PixelFormat::N8_BPP; + else + ePixelFormat = vcl::PixelFormat::N1_BPP; + + maBmp = Bitmap(Size(mnWidth, mnHeight), ePixelFormat); + mpAcc = BitmapScopedWriteAccess(maBmp); + + // mbTransparent is TRUE if at least one colour is transparent + if ( mbTransparent ) + { + maMaskBmp = Bitmap(Size(mnWidth, mnHeight), vcl::PixelFormat::N1_BPP); + mpMaskAcc = BitmapScopedWriteAccess(maMaskBmp); + if ( !mpMaskAcc ) + mbStatus = false; + } + if( mpAcc && mbStatus ) + { + if (mnColors <= 256) // palette is only needed by using less than 257 + { // colors + sal_uInt8 i = 0; + for (auto& elem : maColMap) + { + mpAcc->SetPaletteColor(i, Color(elem.second[1], elem.second[2], elem.second[3])); + //reuse map entry, overwrite color with palette index + elem.second[1] = i; + i++; + } + } + + // now we get the bitmap data + mnIdentifier = XPMPIXELS; + for (sal_uLong i = 0; i < mnHeight; ++i) + { + if ( !ImplGetScanLine( i ) ) + { + mbStatus = false; + break; + } + } + mnIdentifier = XPMEXTENSIONS; + } + } + } + + delete[] mpStringBuf; + delete[] mpTempBuf; + + } + if( mbStatus ) + { + mpAcc.reset(); + if ( mpMaskAcc ) + { + mpMaskAcc.reset(); + rGraphic = Graphic( BitmapEx( maBmp, maMaskBmp ) ); + } + else + { + rGraphic = BitmapEx(maBmp); + } + eReadState = XPMREAD_OK; + } + else + { + mpMaskAcc.reset(); + mpAcc.reset(); + + eReadState = XPMREAD_ERROR; + } + } + else + { + mrIStm.ResetError(); + eReadState = XPMREAD_NEED_MORE; + } + return eReadState; +} + +// ImplGetColor returns various colour values, +// returns TRUE if various colours could be assigned +bool XPMReader::ImplGetColor() +{ + sal_uInt8* pString = mpStringBuf; + if (!ImplGetString()) + return false; + + if (mnStringSize < mnCpp) + return false; + + OString aKey(reinterpret_cast<char*>(pString), mnCpp); + colordata aValue{0}; + bool bStatus = ImplGetColSub(aValue); + if (bStatus) + { + maColMap[aKey] = aValue; + } + return bStatus; +} + +// ImpGetScanLine reads the string mpBufSize and writes the pixel in the +// Bitmap. Parameter nY is the horizontal position. +bool XPMReader::ImplGetScanLine( sal_uLong nY ) +{ + bool bStatus = ImplGetString(); + sal_uInt8* pString = mpStringBuf; + BitmapColor aWhite; + BitmapColor aBlack; + + if ( bStatus ) + { + if ( mpMaskAcc ) + { + aWhite = mpMaskAcc->GetBestMatchingColor( COL_WHITE ); + aBlack = mpMaskAcc->GetBestMatchingColor( COL_BLACK ); + } + if ( mnStringSize != ( mnWidth * mnCpp )) + bStatus = false; + else + { + Scanline pScanline = mpAcc->GetScanline(nY); + Scanline pMaskScanline = mpMaskAcc ? mpMaskAcc->GetScanline(nY) : nullptr; + for (sal_uLong i = 0; i < mnWidth; ++i) + { + OString aKey(reinterpret_cast<char*>(pString), mnCpp); + auto it = maColMap.find(aKey); + if (it != maColMap.end()) + { + if (mnColors > 256) + mpAcc->SetPixelOnData(pScanline, i, Color(it->second[1], it->second[2], it->second[3])); + else + mpAcc->SetPixelOnData(pScanline, i, BitmapColor(it->second[1])); + if (pMaskScanline) + mpMaskAcc->SetPixelOnData(pMaskScanline, i, it->second[0] ? aWhite : aBlack); + } + pString += mnCpp; + } + } + } + return bStatus; +} + +// tries to determine a colour value from mpStringBuf +// if a colour was found the RGB value is written a pDest[1]..pDest[2] +// pDest[0] contains 0xFF if the colour is transparent otherwise 0 + +bool XPMReader::ImplGetColSub(colordata &rDest) +{ + unsigned char cTransparent[] = "None"; + + bool bColStatus = false; + + if ( ImplGetColKey( 'c' ) || ImplGetColKey( 'm' ) || ImplGetColKey( 'g' ) ) + { + // hexentry for RGB or HSV color ? + if (*mpPara == '#') + { + rDest[0] = 0; + bColStatus = true; + switch ( mnParaSize ) + { + case 25 : + ImplGetRGBHex(rDest, 6); + break; + case 13 : + ImplGetRGBHex(rDest, 2); + break; + case 7 : + ImplGetRGBHex(rDest, 0); + break; + default: + bColStatus = false; + break; + } + } + // maybe pixel is transparent + else if ( ImplCompare( &cTransparent[0], mpPara, 4 )) + { + rDest[0] = 0xff; + bColStatus = true; + mbTransparent = true; + } + // last we will try to get the colorname + else if ( mnParaSize > 2 ) // name must enlarge the minimum size + { + sal_uLong i = 0; + while ( true ) + { + if ( pRGBTable[ i ].name == nullptr ) + break; + if ( std::strlen(pRGBTable[i].name) > mnParaSize && + pRGBTable[ i ].name[ mnParaSize ] == 0 ) + { + if ( ImplCompare ( reinterpret_cast<unsigned char const *>(pRGBTable[ i ].name), + mpPara, mnParaSize ) ) + { + bColStatus = true; + rDest[0] = 0; + rDest[1] = pRGBTable[i].red; + rDest[2] = pRGBTable[i].green; + rDest[3] = pRGBTable[i].blue; + break; + } + } + i++; + } + } + } + return bColStatus; +} + +// ImplGetColKey searches string mpStringBuf for a parameter 'nKey' +// and returns a boolean. (if TRUE mpPara and mnParaSize will be set) + +bool XPMReader::ImplGetColKey( sal_uInt8 nKey ) +{ + sal_uInt8 nTemp, nPrev = ' '; + + if (mnStringSize < mnCpp + 1) + return false; + + mpPara = mpStringBuf + mnCpp + 1; + mnParaSize = 0; + + while ( *mpPara != 0 ) + { + if ( *mpPara == nKey ) + { + nTemp = *( mpPara + 1 ); + if ( nTemp == ' ' || nTemp == 0x09 ) + { + if ( nPrev == ' ' || nPrev == 0x09 ) + break; + } + } + nPrev = *mpPara; + mpPara++; + } + if ( *mpPara ) + { + mpPara++; + while ( (*mpPara == ' ') || (*mpPara == 0x09) ) + { + mpPara++; + } + if ( *mpPara != 0 ) + { + while ( *(mpPara+mnParaSize) != ' ' && *(mpPara+mnParaSize) != 0x09 && + *(mpPara+mnParaSize) != 0 ) + { + mnParaSize++; + } + } + } + return mnParaSize != 0; +} + +// ImplGetRGBHex translates the ASCII-Hexadecimalvalue belonging to mpPara +// in a RGB value and writes this to rDest +// below formats should be contained in mpPara: +// if nAdd = 0 : '#12ab12' -> RGB = 0x12, 0xab, 0x12 +// 2 : '#1234abcd1234' " " " " +// 6 : '#12345678abcdefab12345678' " " " " + +void XPMReader::ImplGetRGBHex(colordata &rDest, sal_uLong nAdd) +{ + sal_uInt8* pPtr = mpPara+1; + + for (sal_uLong i = 1; i < 4; ++i) + { + sal_uInt8 nHex = (*pPtr++) - '0'; + if ( nHex > 9 ) + nHex = ((nHex - 'A' + '0') & 7) + 10; + + sal_uInt8 nTemp = (*pPtr++) - '0'; + if ( nTemp > 9 ) + nTemp = ((nTemp - 'A' + '0') & 7) + 10; + nHex = ( nHex << 4 ) + nTemp; + + pPtr += nAdd; + rDest[i] = nHex; + } +} + +// ImplGetUlong returns the value of a up to 6-digit long ASCII-decimal number. + +sal_uLong XPMReader::ImplGetULONG( sal_uLong nPara ) +{ + if ( ImplGetPara ( nPara ) ) + { + sal_uLong nRetValue = 0; + sal_uInt8* pPtr = mpPara; + + if ( ( mnParaSize > 6 ) || ( mnParaSize == 0 ) ) return 0; + for ( sal_uLong i = 0; i < mnParaSize; i++ ) + { + sal_uInt8 j = (*pPtr++) - 48; + if ( j > 9 ) return 0; // ascii is invalid + nRetValue*=10; + nRetValue+=j; + } + return nRetValue; + } + else return 0; +} + +bool XPMReader::ImplCompare(sal_uInt8 const * pSource, sal_uInt8 const * pDest, sal_uLong nSize) +{ + for (sal_uLong i = 0; i < nSize; ++i) + { + if ( ( pSource[i]&~0x20 ) != ( pDest[i]&~0x20 ) ) + { + return false; + } + } + return true; +} + +// ImplGetPara tries to retrieve nNumb (0...x) parameters from mpStringBuf. +// Parameters are separated by spaces or tabs. +// If a parameter was found then the return value is TRUE and mpPara + mnParaSize +// are set. + +bool XPMReader::ImplGetPara ( sal_uLong nNumb ) +{ + sal_uInt8 nByte; + sal_uLong nSize = 0; + sal_uInt8* pPtr = mpStringBuf; + sal_uLong nCount = 0; + + if ( ( *pPtr != ' ' ) && ( *pPtr != 0x09 ) ) + { + mpPara = pPtr; + mnParaSize = 0; + nCount = 0; + } + else + { + mpPara = nullptr; + nCount = 0xffffffff; + } + + while ( nSize < mnStringSize ) + { + nByte = *pPtr; + + if ( mpPara ) + { + if ( ( nByte == ' ' ) || ( nByte == 0x09 ) ) + { + if ( nCount == nNumb ) + break; + else + mpPara = nullptr; + } + else + mnParaSize++; + } + else + { + if ( ( nByte != ' ' ) && ( nByte != 0x09 ) ) + { + mpPara = pPtr; + mnParaSize = 1; + nCount++; + } + } + nSize++; + pPtr++; + } + return ( ( nCount == nNumb ) && mpPara ); +} + +// The next string is read and stored in mpStringBuf (terminated with 0); +// mnStringSize contains the size of the string read. +// Comments like '//' and '/*...*/' are skipped. + +bool XPMReader::ImplGetString() +{ + sal_uInt8 const sID[] = "/* XPM */"; + sal_uInt8* pString = mpStringBuf; + + mnStringSize = 0; + mpStringBuf[0] = 0; + + while( mbStatus && ( mnStatus != XPMFINISHED ) ) + { + if ( mnTempAvail == 0 ) + { + mnTempAvail = mrIStm.ReadBytes( mpTempBuf, XPMTEMPBUFSIZE ); + if ( mnTempAvail == 0 ) + break; + + mpTempPtr = mpTempBuf; + + if ( mnIdentifier == XPMIDENTIFIER ) + { + if ( mnTempAvail <= 50 ) + { + mbStatus = false; // file is too short to be a correct XPM format + break; + } + for ( int i = 0; i < 9; i++ ) // searching for "/* XPM */" + if ( *mpTempPtr++ != sID[i] ) + { + mbStatus = false; + break; + } + mnTempAvail-=9; + mnIdentifier++; + } + } + mcLastByte = mcThisByte; + mcThisByte = *mpTempPtr++; + mnTempAvail--; + + if ( mnStatus & XPMDOUBLE ) + { + if ( mcThisByte == 0x0a ) + mnStatus &=~XPMDOUBLE; + continue; + } + if ( mnStatus & XPMREMARK ) + { + if ( ( mcThisByte == '/' ) && ( mcLastByte == '*' ) ) + mnStatus &=~XPMREMARK; + continue; + } + if ( mnStatus & XPMSTRING ) // characters in string + { + if ( mcThisByte == '"' ) + { + mnStatus &=~XPMSTRING; // end of parameter by eol + break; + } + if ( mnStringSize >= ( XPMSTRINGBUF - 1 ) ) + { + mbStatus = false; + break; + } + *pString++ = mcThisByte; + pString[0] = 0; + mnStringSize++; + continue; + } + else + { // characters beside string + switch ( mcThisByte ) + { + case '*' : + if ( mcLastByte == '/' ) mnStatus |= XPMREMARK; + break; + case '/' : + if ( mcLastByte == '/' ) mnStatus |= XPMDOUBLE; + break; + case '"' : mnStatus |= XPMSTRING; + break; + case '{' : + if ( mnIdentifier == XPMDEFINITION ) + mnIdentifier++; + break; + case '}' : + if ( mnIdentifier == XPMENDEXT ) + mnStatus = XPMFINISHED; + break; + } + } + } + return mbStatus; +} + + +VCL_DLLPUBLIC bool ImportXPM( SvStream& rStm, Graphic& rGraphic ) +{ + std::shared_ptr<GraphicReader> pContext = rGraphic.GetReaderContext(); + rGraphic.SetReaderContext(nullptr); + XPMReader* pXPMReader = dynamic_cast<XPMReader*>( pContext.get() ); + if (!pXPMReader) + { + pContext = std::make_shared<XPMReader>( rStm ); + pXPMReader = static_cast<XPMReader*>( pContext.get() ); + } + + bool bRet = true; + + ReadState eReadState = pXPMReader->ReadXPM( rGraphic ); + + if( eReadState == XPMREAD_ERROR ) + { + bRet = false; + } + else if( eReadState == XPMREAD_NEED_MORE ) + rGraphic.SetReaderContext( pContext ); + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |