1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <filter/XpmReader.hxx>
22 #include <vcl/graph.hxx>
23 #include <tools/stream.hxx>
25 #include <vcl/BitmapWriteAccess.hxx>
27 #include "rgbtable.hxx"
33 #define XPMTEMPBUFSIZE 0x00008000
34 #define XPMSTRINGBUF 0x00008000
36 #define XPMIDENTIFIER 0x00000001 // mnIdentifier includes one of the six phases
37 #define XPMDEFINITION 0x00000002 // the XPM format consists of
38 #define XPMVALUES 0x00000003
39 #define XPMCOLORS 0x00000004
40 #define XPMPIXELS 0x00000005
41 #define XPMEXTENSIONS 0x00000006
42 #define XPMENDEXT 0x00000007
44 #define XPMREMARK 0x00000001 // defines used by mnStatus
45 #define XPMDOUBLE 0x00000002
46 #define XPMSTRING 0x00000004
47 #define XPMFINISHED 0x00000008
59 class BitmapWriteAccess
;
70 BitmapScopedWriteAccess mpWriterAccess
;
72 BitmapScopedWriteAccess mpMaskWriterAccess
;
75 tools::Long mnWidth
= 0;
76 tools::Long mnHeight
= 0;
77 sal_uLong mnColors
= 0;
78 sal_uInt32 mnCpp
= 0; // characters per pix
79 bool mbTransparent
= false;
81 sal_uLong mnStatus
= 0;
82 sal_uLong mnIdentifier
= XPMIDENTIFIER
;
83 sal_uInt8 mcThisByte
= 0;
84 sal_uInt8 mcLastByte
= 0;
85 sal_uLong mnTempAvail
= 0;
86 sal_uInt8
* mpTempBuf
= nullptr;
87 sal_uInt8
* mpTempPtr
= nullptr;
88 // each key is ( mnCpp )Byte(s)-> ASCII entry assigned to the colour
90 // 1 Byte -> 0xFF if colour is transparent
91 // 3 Bytes -> RGB value of the colour
92 typedef std::array
<sal_uInt8
, 4> ColorData
;
93 typedef std::map
<OString
, ColorData
> ColorMap
;
95 sal_uLong mnStringSize
= 0;
96 sal_uInt8
* mpStringBuf
= nullptr;
97 sal_uLong mnParaSize
= 0;
98 sal_uInt8
* mpPara
= nullptr;
100 bool ImplGetString();
102 bool ImplGetScanLine( sal_uLong
);
103 bool ImplGetColSub(ColorData
&rDest
);
104 bool ImplGetColKey( sal_uInt8
);
105 void ImplGetRGBHex(ColorData
&rDest
, sal_uLong
);
106 bool ImplGetPara( sal_uLong numb
);
107 static bool ImplCompare(sal_uInt8
const *, sal_uInt8
const *, sal_uLong
);
108 sal_uLong
ImplGetULONG( sal_uLong nPara
);
111 explicit XPMReader(SvStream
& rStream
);
113 ReadState
ReadXPM(BitmapEx
& rBitmapEx
);
118 XPMReader::XPMReader(SvStream
& rStream
)
120 , mnLastPos(rStream
.Tell())
124 ReadState
XPMReader::ReadXPM(BitmapEx
& rBitmapEx
)
126 if (!mrStream
.good())
127 return XPMREAD_ERROR
;
129 ReadState eReadState
= XPMREAD_ERROR
;
131 mrStream
.Seek( mnLastPos
);
136 mpStringBuf
= new sal_uInt8
[ XPMSTRINGBUF
];
137 mpTempBuf
= new sal_uInt8
[ XPMTEMPBUFSIZE
];
139 mbStatus
= ImplGetString();
142 mnIdentifier
= XPMVALUES
; // fetch Bitmap information
143 mnWidth
= ImplGetULONG( 0 );
144 mnHeight
= ImplGetULONG( 1 );
145 mnColors
= ImplGetULONG( 2 );
146 mnCpp
= ImplGetULONG( 3 );
148 if ( mnColors
> ( SAL_MAX_UINT32
/ ( 4 + mnCpp
) ) )
150 if ( ( mnWidth
* mnCpp
) >= XPMSTRINGBUF
)
152 //xpms are a minimum of one character (one byte) per pixel, so if the file isn't
153 //even that long, it's not all there
154 if (mrStream
.remainingSize() + mnTempAvail
< static_cast<sal_uInt64
>(mnWidth
) * mnHeight
)
156 if ( mbStatus
&& mnWidth
&& mnHeight
&& mnColors
&& mnCpp
)
158 mnIdentifier
= XPMCOLORS
;
160 for (sal_uLong i
= 0; i
< mnColors
; ++i
)
171 // create a 24bit graphic when more as 256 colours present
172 auto ePixelFormat
= vcl::PixelFormat::INVALID
;
173 if ( mnColors
> 256 )
174 ePixelFormat
= vcl::PixelFormat::N24_BPP
;
176 ePixelFormat
= vcl::PixelFormat::N8_BPP
;
178 maBitmap
= Bitmap(Size(mnWidth
, mnHeight
), ePixelFormat
);
179 mpWriterAccess
= maBitmap
;
181 // mbTransparent is TRUE if at least one colour is transparent
184 maMaskBitmap
= Bitmap(Size(mnWidth
, mnHeight
), vcl::PixelFormat::N8_BPP
, &Bitmap::GetGreyPalette(256));
185 mpMaskWriterAccess
= maMaskBitmap
;
186 if (!mpMaskWriterAccess
)
189 if (mpWriterAccess
&& mbStatus
)
191 if (mnColors
<= 256) // palette is only needed by using less than 257
194 for (auto& elem
: maColMap
)
196 mpWriterAccess
->SetPaletteColor(i
, Color(elem
.second
[1], elem
.second
[2], elem
.second
[3]));
197 //reuse map entry, overwrite color with palette index
203 // now we get the bitmap data
204 mnIdentifier
= XPMPIXELS
;
205 for (tools::Long i
= 0; i
< mnHeight
; ++i
)
207 if ( !ImplGetScanLine( i
) )
213 mnIdentifier
= XPMEXTENSIONS
;
218 delete[] mpStringBuf
;
224 mpWriterAccess
.reset();
225 if (mpMaskWriterAccess
)
227 mpMaskWriterAccess
.reset();
228 rBitmapEx
= BitmapEx(maBitmap
, maMaskBitmap
);
232 rBitmapEx
= BitmapEx(maBitmap
);
234 eReadState
= XPMREAD_OK
;
238 mpMaskWriterAccess
.reset();
239 mpWriterAccess
.reset();
244 // ImplGetColor returns various colour values,
245 // returns TRUE if various colours could be assigned
246 bool XPMReader::ImplGetColor()
248 sal_uInt8
* pString
= mpStringBuf
;
249 if (!ImplGetString())
252 if (mnStringSize
< mnCpp
)
255 OString
aKey(reinterpret_cast<char*>(pString
), mnCpp
);
257 bool bStatus
= ImplGetColSub(aValue
);
260 maColMap
[aKey
] = aValue
;
265 // ImpGetScanLine reads the string mpBufSize and writes the pixel in the
266 // Bitmap. Parameter nY is the horizontal position.
267 bool XPMReader::ImplGetScanLine( sal_uLong nY
)
269 bool bStatus
= ImplGetString();
270 sal_uInt8
* pString
= mpStringBuf
;
276 if (mpMaskWriterAccess
)
278 aWhite
= mpMaskWriterAccess
->GetBestMatchingColor( COL_WHITE
);
279 aBlack
= mpMaskWriterAccess
->GetBestMatchingColor( COL_BLACK
);
281 if (mnStringSize
!= (sal_uLong(mnWidth
) * mnCpp
))
285 Scanline pScanline
= mpWriterAccess
->GetScanline(nY
);
286 Scanline pMaskScanline
= mpMaskWriterAccess
? mpMaskWriterAccess
->GetScanline(nY
) : nullptr;
287 for (tools::Long i
= 0; i
< mnWidth
; ++i
)
289 OString
aKey(reinterpret_cast<char*>(pString
), mnCpp
);
290 auto it
= maColMap
.find(aKey
);
291 if (it
!= maColMap
.end())
294 mpWriterAccess
->SetPixelOnData(pScanline
, i
, Color(it
->second
[1], it
->second
[2], it
->second
[3]));
296 mpWriterAccess
->SetPixelOnData(pScanline
, i
, BitmapColor(it
->second
[1]));
298 mpMaskWriterAccess
->SetPixelOnData(pMaskScanline
, i
, it
->second
[0] ? aWhite
: aBlack
);
307 // tries to determine a colour value from mpStringBuf
308 // if a colour was found the RGB value is written a pDest[1]..pDest[2]
309 // pDest[0] contains 0xFF if the colour is transparent otherwise 0
311 bool XPMReader::ImplGetColSub(ColorData
& rDest
)
313 unsigned char cTransparent
[] = "None";
315 bool bColStatus
= false;
317 if ( ImplGetColKey( 'c' ) || ImplGetColKey( 'm' ) || ImplGetColKey( 'g' ) )
319 // hexentry for RGB or HSV color ?
324 switch ( mnParaSize
)
327 ImplGetRGBHex(rDest
, 6);
330 ImplGetRGBHex(rDest
, 2);
333 ImplGetRGBHex(rDest
, 0);
340 // maybe pixel is transparent
341 else if ( ImplCompare( &cTransparent
[0], mpPara
, 4 ))
345 mbTransparent
= true;
347 // last we will try to get the colorname
348 else if ( mnParaSize
> 2 ) // name must enlarge the minimum size
353 if ( pRGBTable
[ i
].name
== nullptr )
355 if ( std::strlen(pRGBTable
[i
].name
) > mnParaSize
&&
356 pRGBTable
[ i
].name
[ mnParaSize
] == 0 )
358 if ( ImplCompare ( reinterpret_cast<unsigned char const *>(pRGBTable
[ i
].name
),
359 mpPara
, mnParaSize
) )
363 rDest
[1] = pRGBTable
[i
].red
;
364 rDest
[2] = pRGBTable
[i
].green
;
365 rDest
[3] = pRGBTable
[i
].blue
;
376 // ImplGetColKey searches string mpStringBuf for a parameter 'nKey'
377 // and returns a boolean. (if TRUE mpPara and mnParaSize will be set)
379 bool XPMReader::ImplGetColKey( sal_uInt8 nKey
)
381 sal_uInt8 nTemp
, nPrev
= ' ';
383 if (mnStringSize
< mnCpp
+ 1)
386 mpPara
= mpStringBuf
+ mnCpp
+ 1;
389 while ( *mpPara
!= 0 )
391 if ( *mpPara
== nKey
)
393 nTemp
= *( mpPara
+ 1 );
394 if ( nTemp
== ' ' || nTemp
== 0x09 )
396 if ( nPrev
== ' ' || nPrev
== 0x09 )
406 while ( (*mpPara
== ' ') || (*mpPara
== 0x09) )
412 while ( *(mpPara
+mnParaSize
) != ' ' && *(mpPara
+mnParaSize
) != 0x09 &&
413 *(mpPara
+mnParaSize
) != 0 )
419 return mnParaSize
!= 0;
422 // ImplGetRGBHex translates the ASCII-Hexadecimalvalue belonging to mpPara
423 // in a RGB value and writes this to rDest
424 // below formats should be contained in mpPara:
425 // if nAdd = 0 : '#12ab12' -> RGB = 0x12, 0xab, 0x12
426 // 2 : '#1234abcd1234' " " " "
427 // 6 : '#12345678abcdefab12345678' " " " "
429 void XPMReader::ImplGetRGBHex(ColorData
& rDest
, sal_uLong nAdd
)
431 sal_uInt8
* pPtr
= mpPara
+1;
433 for (sal_uLong i
= 1; i
< 4; ++i
)
435 sal_uInt8 nHex
= (*pPtr
++) - '0';
437 nHex
= ((nHex
- 'A' + '0') & 7) + 10;
439 sal_uInt8 nTemp
= (*pPtr
++) - '0';
441 nTemp
= ((nTemp
- 'A' + '0') & 7) + 10;
442 nHex
= ( nHex
<< 4 ) + nTemp
;
449 // ImplGetUlong returns the value of a up to 6-digit long ASCII-decimal number.
451 sal_uLong
XPMReader::ImplGetULONG( sal_uLong nPara
)
453 if ( ImplGetPara ( nPara
) )
455 sal_uLong nRetValue
= 0;
456 sal_uInt8
* pPtr
= mpPara
;
458 if ( ( mnParaSize
> 6 ) || ( mnParaSize
== 0 ) ) return 0;
459 for ( sal_uLong i
= 0; i
< mnParaSize
; i
++ )
461 sal_uInt8 j
= (*pPtr
++) - 48;
462 if ( j
> 9 ) return 0; // ascii is invalid
471 bool XPMReader::ImplCompare(sal_uInt8
const * pSource
, sal_uInt8
const * pDest
, sal_uLong nSize
)
473 for (sal_uLong i
= 0; i
< nSize
; ++i
)
475 if ( ( pSource
[i
]&~0x20 ) != ( pDest
[i
]&~0x20 ) )
483 // ImplGetPara tries to retrieve nNumb (0...x) parameters from mpStringBuf.
484 // Parameters are separated by spaces or tabs.
485 // If a parameter was found then the return value is TRUE and mpPara + mnParaSize
488 bool XPMReader::ImplGetPara ( sal_uLong nNumb
)
492 sal_uInt8
* pPtr
= mpStringBuf
;
493 sal_uLong nCount
= 0;
495 if ( ( *pPtr
!= ' ' ) && ( *pPtr
!= 0x09 ) )
507 while ( nSize
< mnStringSize
)
513 if ( ( nByte
== ' ' ) || ( nByte
== 0x09 ) )
515 if ( nCount
== nNumb
)
525 if ( ( nByte
!= ' ' ) && ( nByte
!= 0x09 ) )
535 return ( ( nCount
== nNumb
) && mpPara
);
538 // The next string is read and stored in mpStringBuf (terminated with 0);
539 // mnStringSize contains the size of the string read.
540 // Comments like '//' and '/*...*/' are skipped.
542 bool XPMReader::ImplGetString()
544 sal_uInt8
const sID
[] = "/* XPM */";
545 sal_uInt8
* pString
= mpStringBuf
;
550 while( mbStatus
&& ( mnStatus
!= XPMFINISHED
) )
552 if ( mnTempAvail
== 0 )
554 mnTempAvail
= mrStream
.ReadBytes( mpTempBuf
, XPMTEMPBUFSIZE
);
555 if ( mnTempAvail
== 0 )
558 mpTempPtr
= mpTempBuf
;
560 if ( mnIdentifier
== XPMIDENTIFIER
)
562 if ( mnTempAvail
<= 50 )
564 mbStatus
= false; // file is too short to be a correct XPM format
567 for ( int i
= 0; i
< 9; i
++ ) // searching for "/* XPM */"
568 if ( *mpTempPtr
++ != sID
[i
] )
577 mcLastByte
= mcThisByte
;
578 mcThisByte
= *mpTempPtr
++;
581 if ( mnStatus
& XPMDOUBLE
)
583 if ( mcThisByte
== 0x0a )
584 mnStatus
&=~XPMDOUBLE
;
587 if ( mnStatus
& XPMREMARK
)
589 if ( ( mcThisByte
== '/' ) && ( mcLastByte
== '*' ) )
590 mnStatus
&=~XPMREMARK
;
593 if ( mnStatus
& XPMSTRING
) // characters in string
595 if ( mcThisByte
== '"' )
597 mnStatus
&=~XPMSTRING
; // end of parameter by eol
600 if ( mnStringSize
>= ( XPMSTRINGBUF
- 1 ) )
605 *pString
++ = mcThisByte
;
611 { // characters beside string
612 switch ( mcThisByte
)
615 if ( mcLastByte
== '/' ) mnStatus
|= XPMREMARK
;
618 if ( mcLastByte
== '/' ) mnStatus
|= XPMDOUBLE
;
620 case '"' : mnStatus
|= XPMSTRING
;
623 if ( mnIdentifier
== XPMDEFINITION
)
627 if ( mnIdentifier
== XPMENDEXT
)
628 mnStatus
= XPMFINISHED
;
637 VCL_DLLPUBLIC
bool ImportXPM(SvStream
& rStream
, Graphic
& rGraphic
)
639 XPMReader
aXPMReader(rStream
);
642 ReadState eReadState
= aXPMReader
.ReadXPM(aBitmapEx
);
644 if (eReadState
== XPMREAD_ERROR
)
646 rGraphic
= Graphic(aBitmapEx
);
650 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */