LibreOffice Module vcl (master) 1
xpmread.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20#include <filter/XpmReader.hxx>
21
22#include <vcl/graph.hxx>
23#include <tools/stream.hxx>
24
27
28#include "rgbtable.hxx"
29
30#include <cstring>
31#include <array>
32#include <map>
33
34#define XPMTEMPBUFSIZE 0x00008000
35#define XPMSTRINGBUF 0x00008000
36
37#define XPMIDENTIFIER 0x00000001 // mnIdentifier includes one of the six phases
38#define XPMDEFINITION 0x00000002 // the XPM format consists of
39#define XPMVALUES 0x00000003
40#define XPMCOLORS 0x00000004
41#define XPMPIXELS 0x00000005
42#define XPMEXTENSIONS 0x00000006
43#define XPMENDEXT 0x00000007
44
45#define XPMREMARK 0x00000001 // defines used by mnStatus
46#define XPMDOUBLE 0x00000002
47#define XPMSTRING 0x00000004
48#define XPMFINISHED 0x00000008
49
50namespace {
51
52enum ReadState
53{
54 XPMREAD_OK,
55 XPMREAD_ERROR,
56 XPMREAD_NEED_MORE
57};
58
59}
60
62class Graphic;
63
64namespace {
65
66class XPMReader : public GraphicReader
67{
68private:
69
70 SvStream& mrIStm;
71 Bitmap maBmp;
73 Bitmap maMaskBmp;
75 tools::Long mnLastPos;
76
79 sal_uLong mnColors;
80 sal_uInt32 mnCpp; // characters per pix
81 bool mbTransparent;
82 bool mbStatus;
83 sal_uLong mnStatus;
84 sal_uLong mnIdentifier;
85 sal_uInt8 mcThisByte;
86 sal_uInt8 mcLastByte;
87 sal_uLong mnTempAvail;
88 sal_uInt8* mpTempBuf;
89 sal_uInt8* mpTempPtr;
90 // each key is ( mnCpp )Byte(s)-> ASCII entry assigned to the colour
91 // each colordata is
92 // 1 Byte -> 0xFF if colour is transparent
93 // 3 Bytes -> RGB value of the colour
94 typedef std::array<sal_uInt8,4> colordata;
95 typedef std::map<OString, colordata> colormap;
96 colormap maColMap;
97 sal_uLong mnStringSize;
98 sal_uInt8* mpStringBuf;
99 sal_uLong mnParaSize;
100 sal_uInt8* mpPara;
101
102 bool ImplGetString();
103 bool ImplGetColor();
104 bool ImplGetScanLine( sal_uLong );
105 bool ImplGetColSub(colordata &rDest);
106 bool ImplGetColKey( sal_uInt8 );
107 void ImplGetRGBHex(colordata &rDest, sal_uLong);
108 bool ImplGetPara( sal_uLong numb );
109 static bool ImplCompare(sal_uInt8 const *, sal_uInt8 const *, sal_uLong);
110 sal_uLong ImplGetULONG( sal_uLong nPara );
111
112public:
113 explicit XPMReader( SvStream& rStm );
114
115 ReadState ReadXPM( Graphic& rGraphic );
116};
117
118}
119
120XPMReader::XPMReader(SvStream& rStm)
121 : mrIStm(rStm)
122 , mnLastPos(rStm.Tell())
123 , mnWidth(0)
124 , mnHeight(0)
125 , mnColors(0)
126 , mnCpp(0)
127 , mbTransparent(false)
128 , mbStatus(true)
129 , mnStatus( 0 )
130 , mnIdentifier(XPMIDENTIFIER)
131 , mcThisByte(0)
132 , mcLastByte(0)
133 , mnTempAvail(0)
134 , mpTempBuf(nullptr)
135 , mpTempPtr(nullptr)
136 , mnStringSize(0)
137 , mpStringBuf(nullptr)
138 , mnParaSize(0)
139 , mpPara(nullptr)
140{
141}
142
143ReadState XPMReader::ReadXPM( Graphic& rGraphic )
144{
145 ReadState eReadState;
146 sal_uInt8 cDummy;
147
148 // check if we can real ALL
149 mrIStm.Seek( STREAM_SEEK_TO_END );
150 mrIStm.ReadUChar( cDummy );
151
152 // if we could not read all
153 // return and wait for new data
154 if ( mrIStm.GetError() != ERRCODE_IO_PENDING )
155 {
156 mrIStm.Seek( mnLastPos );
157 mbStatus = true;
158
159 if ( mbStatus )
160 {
161 mpStringBuf = new sal_uInt8 [ XPMSTRINGBUF ];
162 mpTempBuf = new sal_uInt8 [ XPMTEMPBUFSIZE ];
163
164 mbStatus = ImplGetString();
165 if ( mbStatus )
166 {
167 mnIdentifier = XPMVALUES; // fetch Bitmap information
168 mnWidth = ImplGetULONG( 0 );
169 mnHeight = ImplGetULONG( 1 );
170 mnColors = ImplGetULONG( 2 );
171 mnCpp = ImplGetULONG( 3 );
172 }
173 if ( mnColors > ( SAL_MAX_UINT32 / ( 4 + mnCpp ) ) )
174 mbStatus = false;
175 if ( ( mnWidth * mnCpp ) >= XPMSTRINGBUF )
176 mbStatus = false;
177 //xpms are a minimum of one character (one byte) per pixel, so if the file isn't
178 //even that long, it's not all there
179 if (mrIStm.remainingSize() + mnTempAvail < static_cast<sal_uInt64>(mnWidth) * mnHeight)
180 mbStatus = false;
181 if ( mbStatus && mnWidth && mnHeight && mnColors && mnCpp )
182 {
183 mnIdentifier = XPMCOLORS;
184
185 for (sal_uLong i = 0; i < mnColors; ++i)
186 {
187 if (!ImplGetColor())
188 {
189 mbStatus = false;
190 break;
191 }
192 }
193
194 if ( mbStatus )
195 {
196 // create a 24bit graphic when more as 256 colours present
197 auto ePixelFormat = vcl::PixelFormat::INVALID;
198 if ( mnColors > 256 )
199 ePixelFormat = vcl::PixelFormat::N24_BPP;
200 else
201 ePixelFormat = vcl::PixelFormat::N8_BPP;
202
203 maBmp = Bitmap(Size(mnWidth, mnHeight), ePixelFormat);
204 mpAcc = BitmapScopedWriteAccess(maBmp);
205
206 // mbTransparent is TRUE if at least one colour is transparent
207 if ( mbTransparent )
208 {
209 maMaskBmp = Bitmap(Size(mnWidth, mnHeight), vcl::PixelFormat::N8_BPP, &Bitmap::GetGreyPalette(256));
210 mpMaskAcc = BitmapScopedWriteAccess(maMaskBmp);
211 if ( !mpMaskAcc )
212 mbStatus = false;
213 }
214 if( mpAcc && mbStatus )
215 {
216 if (mnColors <= 256) // palette is only needed by using less than 257
217 { // colors
218 sal_uInt8 i = 0;
219 for (auto& elem : maColMap)
220 {
221 mpAcc->SetPaletteColor(i, Color(elem.second[1], elem.second[2], elem.second[3]));
222 //reuse map entry, overwrite color with palette index
223 elem.second[1] = i;
224 i++;
225 }
226 }
227
228 // now we get the bitmap data
229 mnIdentifier = XPMPIXELS;
230 for (sal_uLong i = 0; i < mnHeight; ++i)
231 {
232 if ( !ImplGetScanLine( i ) )
233 {
234 mbStatus = false;
235 break;
236 }
237 }
238 mnIdentifier = XPMEXTENSIONS;
239 }
240 }
241 }
242
243 delete[] mpStringBuf;
244 delete[] mpTempBuf;
245
246 }
247 if( mbStatus )
248 {
249 mpAcc.reset();
250 if ( mpMaskAcc )
251 {
252 mpMaskAcc.reset();
253 rGraphic = Graphic( BitmapEx( maBmp, maMaskBmp ) );
254 }
255 else
256 {
257 rGraphic = BitmapEx(maBmp);
258 }
259 eReadState = XPMREAD_OK;
260 }
261 else
262 {
263 mpMaskAcc.reset();
264 mpAcc.reset();
265
266 eReadState = XPMREAD_ERROR;
267 }
268 }
269 else
270 {
271 mrIStm.ResetError();
272 eReadState = XPMREAD_NEED_MORE;
273 }
274 return eReadState;
275}
276
277// ImplGetColor returns various colour values,
278// returns TRUE if various colours could be assigned
279bool XPMReader::ImplGetColor()
280{
281 sal_uInt8* pString = mpStringBuf;
282 if (!ImplGetString())
283 return false;
284
285 if (mnStringSize < mnCpp)
286 return false;
287
288 OString aKey(reinterpret_cast<char*>(pString), mnCpp);
289 colordata aValue{0};
290 bool bStatus = ImplGetColSub(aValue);
291 if (bStatus)
292 {
293 maColMap[aKey] = aValue;
294 }
295 return bStatus;
296}
297
298// ImpGetScanLine reads the string mpBufSize and writes the pixel in the
299// Bitmap. Parameter nY is the horizontal position.
300bool XPMReader::ImplGetScanLine( sal_uLong nY )
301{
302 bool bStatus = ImplGetString();
303 sal_uInt8* pString = mpStringBuf;
304 BitmapColor aWhite;
305 BitmapColor aBlack;
306
307 if ( bStatus )
308 {
309 if ( mpMaskAcc )
310 {
311 aWhite = mpMaskAcc->GetBestMatchingColor( COL_WHITE );
312 aBlack = mpMaskAcc->GetBestMatchingColor( COL_BLACK );
313 }
314 if ( mnStringSize != ( mnWidth * mnCpp ))
315 bStatus = false;
316 else
317 {
318 Scanline pScanline = mpAcc->GetScanline(nY);
319 Scanline pMaskScanline = mpMaskAcc ? mpMaskAcc->GetScanline(nY) : nullptr;
320 for (sal_uLong i = 0; i < mnWidth; ++i)
321 {
322 OString aKey(reinterpret_cast<char*>(pString), mnCpp);
323 auto it = maColMap.find(aKey);
324 if (it != maColMap.end())
325 {
326 if (mnColors > 256)
327 mpAcc->SetPixelOnData(pScanline, i, Color(it->second[1], it->second[2], it->second[3]));
328 else
329 mpAcc->SetPixelOnData(pScanline, i, BitmapColor(it->second[1]));
330 if (pMaskScanline)
331 mpMaskAcc->SetPixelOnData(pMaskScanline, i, it->second[0] ? aWhite : aBlack);
332 }
333 pString += mnCpp;
334 }
335 }
336 }
337 return bStatus;
338}
339
340// tries to determine a colour value from mpStringBuf
341// if a colour was found the RGB value is written a pDest[1]..pDest[2]
342// pDest[0] contains 0xFF if the colour is transparent otherwise 0
343
344bool XPMReader::ImplGetColSub(colordata &rDest)
345{
346 unsigned char cTransparent[] = "None";
347
348 bool bColStatus = false;
349
350 if ( ImplGetColKey( 'c' ) || ImplGetColKey( 'm' ) || ImplGetColKey( 'g' ) )
351 {
352 // hexentry for RGB or HSV color ?
353 if (*mpPara == '#')
354 {
355 rDest[0] = 0;
356 bColStatus = true;
357 switch ( mnParaSize )
358 {
359 case 25 :
360 ImplGetRGBHex(rDest, 6);
361 break;
362 case 13 :
363 ImplGetRGBHex(rDest, 2);
364 break;
365 case 7 :
366 ImplGetRGBHex(rDest, 0);
367 break;
368 default:
369 bColStatus = false;
370 break;
371 }
372 }
373 // maybe pixel is transparent
374 else if ( ImplCompare( &cTransparent[0], mpPara, 4 ))
375 {
376 rDest[0] = 0xff;
377 bColStatus = true;
378 mbTransparent = true;
379 }
380 // last we will try to get the colorname
381 else if ( mnParaSize > 2 ) // name must enlarge the minimum size
382 {
383 sal_uLong i = 0;
384 while ( true )
385 {
386 if ( pRGBTable[ i ].name == nullptr )
387 break;
388 if ( std::strlen(pRGBTable[i].name) > mnParaSize &&
389 pRGBTable[ i ].name[ mnParaSize ] == 0 )
390 {
391 if ( ImplCompare ( reinterpret_cast<unsigned char const *>(pRGBTable[ i ].name),
392 mpPara, mnParaSize ) )
393 {
394 bColStatus = true;
395 rDest[0] = 0;
396 rDest[1] = pRGBTable[i].red;
397 rDest[2] = pRGBTable[i].green;
398 rDest[3] = pRGBTable[i].blue;
399 break;
400 }
401 }
402 i++;
403 }
404 }
405 }
406 return bColStatus;
407}
408
409// ImplGetColKey searches string mpStringBuf for a parameter 'nKey'
410// and returns a boolean. (if TRUE mpPara and mnParaSize will be set)
411
412bool XPMReader::ImplGetColKey( sal_uInt8 nKey )
413{
414 sal_uInt8 nTemp, nPrev = ' ';
415
416 if (mnStringSize < mnCpp + 1)
417 return false;
418
419 mpPara = mpStringBuf + mnCpp + 1;
420 mnParaSize = 0;
421
422 while ( *mpPara != 0 )
423 {
424 if ( *mpPara == nKey )
425 {
426 nTemp = *( mpPara + 1 );
427 if ( nTemp == ' ' || nTemp == 0x09 )
428 {
429 if ( nPrev == ' ' || nPrev == 0x09 )
430 break;
431 }
432 }
433 nPrev = *mpPara;
434 mpPara++;
435 }
436 if ( *mpPara )
437 {
438 mpPara++;
439 while ( (*mpPara == ' ') || (*mpPara == 0x09) )
440 {
441 mpPara++;
442 }
443 if ( *mpPara != 0 )
444 {
445 while ( *(mpPara+mnParaSize) != ' ' && *(mpPara+mnParaSize) != 0x09 &&
446 *(mpPara+mnParaSize) != 0 )
447 {
448 mnParaSize++;
449 }
450 }
451 }
452 return mnParaSize != 0;
453}
454
455// ImplGetRGBHex translates the ASCII-Hexadecimalvalue belonging to mpPara
456// in a RGB value and writes this to rDest
457// below formats should be contained in mpPara:
458// if nAdd = 0 : '#12ab12' -> RGB = 0x12, 0xab, 0x12
459// 2 : '#1234abcd1234' " " " "
460// 6 : '#12345678abcdefab12345678' " " " "
461
462void XPMReader::ImplGetRGBHex(colordata &rDest, sal_uLong nAdd)
463{
464 sal_uInt8* pPtr = mpPara+1;
465
466 for (sal_uLong i = 1; i < 4; ++i)
467 {
468 sal_uInt8 nHex = (*pPtr++) - '0';
469 if ( nHex > 9 )
470 nHex = ((nHex - 'A' + '0') & 7) + 10;
471
472 sal_uInt8 nTemp = (*pPtr++) - '0';
473 if ( nTemp > 9 )
474 nTemp = ((nTemp - 'A' + '0') & 7) + 10;
475 nHex = ( nHex << 4 ) + nTemp;
476
477 pPtr += nAdd;
478 rDest[i] = nHex;
479 }
480}
481
482// ImplGetUlong returns the value of a up to 6-digit long ASCII-decimal number.
483
484sal_uLong XPMReader::ImplGetULONG( sal_uLong nPara )
485{
486 if ( ImplGetPara ( nPara ) )
487 {
488 sal_uLong nRetValue = 0;
489 sal_uInt8* pPtr = mpPara;
490
491 if ( ( mnParaSize > 6 ) || ( mnParaSize == 0 ) ) return 0;
492 for ( sal_uLong i = 0; i < mnParaSize; i++ )
493 {
494 sal_uInt8 j = (*pPtr++) - 48;
495 if ( j > 9 ) return 0; // ascii is invalid
496 nRetValue*=10;
497 nRetValue+=j;
498 }
499 return nRetValue;
500 }
501 else return 0;
502}
503
504bool XPMReader::ImplCompare(sal_uInt8 const * pSource, sal_uInt8 const * pDest, sal_uLong nSize)
505{
506 for (sal_uLong i = 0; i < nSize; ++i)
507 {
508 if ( ( pSource[i]&~0x20 ) != ( pDest[i]&~0x20 ) )
509 {
510 return false;
511 }
512 }
513 return true;
514}
515
516// ImplGetPara tries to retrieve nNumb (0...x) parameters from mpStringBuf.
517// Parameters are separated by spaces or tabs.
518// If a parameter was found then the return value is TRUE and mpPara + mnParaSize
519// are set.
520
521bool XPMReader::ImplGetPara ( sal_uLong nNumb )
522{
523 sal_uInt8 nByte;
524 sal_uLong nSize = 0;
525 sal_uInt8* pPtr = mpStringBuf;
526 sal_uLong nCount = 0;
527
528 if ( ( *pPtr != ' ' ) && ( *pPtr != 0x09 ) )
529 {
530 mpPara = pPtr;
531 mnParaSize = 0;
532 nCount = 0;
533 }
534 else
535 {
536 mpPara = nullptr;
537 nCount = 0xffffffff;
538 }
539
540 while ( nSize < mnStringSize )
541 {
542 nByte = *pPtr;
543
544 if ( mpPara )
545 {
546 if ( ( nByte == ' ' ) || ( nByte == 0x09 ) )
547 {
548 if ( nCount == nNumb )
549 break;
550 else
551 mpPara = nullptr;
552 }
553 else
554 mnParaSize++;
555 }
556 else
557 {
558 if ( ( nByte != ' ' ) && ( nByte != 0x09 ) )
559 {
560 mpPara = pPtr;
561 mnParaSize = 1;
562 nCount++;
563 }
564 }
565 nSize++;
566 pPtr++;
567 }
568 return ( ( nCount == nNumb ) && mpPara );
569}
570
571// The next string is read and stored in mpStringBuf (terminated with 0);
572// mnStringSize contains the size of the string read.
573// Comments like '//' and '/*...*/' are skipped.
574
575bool XPMReader::ImplGetString()
576{
577 sal_uInt8 const sID[] = "/* XPM */";
578 sal_uInt8* pString = mpStringBuf;
579
580 mnStringSize = 0;
581 mpStringBuf[0] = 0;
582
583 while( mbStatus && ( mnStatus != XPMFINISHED ) )
584 {
585 if ( mnTempAvail == 0 )
586 {
587 mnTempAvail = mrIStm.ReadBytes( mpTempBuf, XPMTEMPBUFSIZE );
588 if ( mnTempAvail == 0 )
589 break;
590
591 mpTempPtr = mpTempBuf;
592
593 if ( mnIdentifier == XPMIDENTIFIER )
594 {
595 if ( mnTempAvail <= 50 )
596 {
597 mbStatus = false; // file is too short to be a correct XPM format
598 break;
599 }
600 for ( int i = 0; i < 9; i++ ) // searching for "/* XPM */"
601 if ( *mpTempPtr++ != sID[i] )
602 {
603 mbStatus = false;
604 break;
605 }
606 mnTempAvail-=9;
607 mnIdentifier++;
608 }
609 }
610 mcLastByte = mcThisByte;
611 mcThisByte = *mpTempPtr++;
612 mnTempAvail--;
613
614 if ( mnStatus & XPMDOUBLE )
615 {
616 if ( mcThisByte == 0x0a )
617 mnStatus &=~XPMDOUBLE;
618 continue;
619 }
620 if ( mnStatus & XPMREMARK )
621 {
622 if ( ( mcThisByte == '/' ) && ( mcLastByte == '*' ) )
623 mnStatus &=~XPMREMARK;
624 continue;
625 }
626 if ( mnStatus & XPMSTRING ) // characters in string
627 {
628 if ( mcThisByte == '"' )
629 {
630 mnStatus &=~XPMSTRING; // end of parameter by eol
631 break;
632 }
633 if ( mnStringSize >= ( XPMSTRINGBUF - 1 ) )
634 {
635 mbStatus = false;
636 break;
637 }
638 *pString++ = mcThisByte;
639 pString[0] = 0;
640 mnStringSize++;
641 continue;
642 }
643 else
644 { // characters beside string
645 switch ( mcThisByte )
646 {
647 case '*' :
648 if ( mcLastByte == '/' ) mnStatus |= XPMREMARK;
649 break;
650 case '/' :
651 if ( mcLastByte == '/' ) mnStatus |= XPMDOUBLE;
652 break;
653 case '"' : mnStatus |= XPMSTRING;
654 break;
655 case '{' :
656 if ( mnIdentifier == XPMDEFINITION )
657 mnIdentifier++;
658 break;
659 case '}' :
660 if ( mnIdentifier == XPMENDEXT )
661 mnStatus = XPMFINISHED;
662 break;
663 }
664 }
665 }
666 return mbStatus;
667}
668
669
670VCL_DLLPUBLIC bool ImportXPM( SvStream& rStm, Graphic& rGraphic )
671{
672 std::shared_ptr<GraphicReader> pContext = rGraphic.GetReaderContext();
673 rGraphic.SetReaderContext(nullptr);
674 XPMReader* pXPMReader = dynamic_cast<XPMReader*>( pContext.get() );
675 if (!pXPMReader)
676 {
677 pContext = std::make_shared<XPMReader>( rStm );
678 pXPMReader = static_cast<XPMReader*>( pContext.get() );
679 }
680
681 bool bRet = true;
682
683 ReadState eReadState = pXPMReader->ReadXPM( rGraphic );
684
685 if( eReadState == XPMREAD_ERROR )
686 {
687 bRet = false;
688 }
689 else if( eReadState == XPMREAD_NEED_MORE )
690 rGraphic.SetReaderContext( pContext );
691
692 return bRet;
693}
694
695/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
vcl::ScopedBitmapAccess< BitmapWriteAccess, Bitmap, &Bitmap::AcquireWriteAccess > BitmapScopedWriteAccess
ReadState
Definition: JpegReader.hxx:31
sal_uInt8 * Scanline
Definition: Scanline.hxx:26
static const BitmapPalette & GetGreyPalette(int nEntries)
void SetReaderContext(const std::shared_ptr< GraphicReader > &pReader)
Definition: graph.cxx:484
std::shared_ptr< GraphicReader > & GetReaderContext()
Definition: graph.cxx:479
This template handles BitmapAccess the RAII way.
int nCount
#define VCL_DLLPUBLIC
Definition: dllapi.h:29
int i
long Long
const XPMRGBTab pRGBTable[]
Definition: rgbtable.hxx:32
double mnWidth
double mnHeight
sal_uIntPtr sal_uLong
sal_uInt8 blue
Definition: rgbtable.hxx:29
sal_uInt8 red
Definition: rgbtable.hxx:27
sal_uInt8 green
Definition: rgbtable.hxx:28
unsigned char sal_uInt8
#define XPMSTRINGBUF
Definition: xpmread.cxx:35
#define XPMENDEXT
Definition: xpmread.cxx:43
#define XPMPIXELS
Definition: xpmread.cxx:41
#define XPMDOUBLE
Definition: xpmread.cxx:46
#define XPMCOLORS
Definition: xpmread.cxx:40
#define XPMTEMPBUFSIZE
Definition: xpmread.cxx:34
#define XPMFINISHED
Definition: xpmread.cxx:48
#define XPMREMARK
Definition: xpmread.cxx:45
#define XPMDEFINITION
Definition: xpmread.cxx:38
#define XPMIDENTIFIER
Definition: xpmread.cxx:37
VCL_DLLPUBLIC bool ImportXPM(SvStream &rStm, Graphic &rGraphic)
Definition: xpmread.cxx:670
#define XPMEXTENSIONS
Definition: xpmread.cxx:42
#define XPMVALUES
Definition: xpmread.cxx:39
#define XPMSTRING
Definition: xpmread.cxx:47