1 /*
2  *  Copyright (C) 2005-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "DVDDemuxSPU.h"
10 
11 #include "DVDCodecs/Overlay/DVDOverlaySpu.h"
12 #include "cores/VideoPlayer/Interface/TimingConstants.h"
13 #include "utils/log.h"
14 
15 #include <locale.h>
16 #include <stdlib.h>
17 
18 #undef ALIGN
19 #define ALIGN(value, alignment) (((value)+((alignment)-1))&~((alignment)-1))
20 
21 // #define SPU_DEBUG
22 
DebugLog(const char * format,...)23 void DebugLog(const char *format, ...)
24 {
25 #ifdef SPU_DEBUG
26   static char temp_spubuffer[1024];
27   va_list va;
28 
29   va_start(va, format);
30   _vsnprintf(temp_spubuffer, 1024, format, va);
31   va_end(va);
32 
33   CLog::Log(LOGDEBUG,temp_spubuffer);
34 #endif
35 }
36 
CDVDDemuxSPU()37 CDVDDemuxSPU::CDVDDemuxSPU()
38 {
39   memset(&m_spuData, 0, sizeof(m_spuData));
40   memset(m_clut, 0, sizeof(m_clut));
41   m_bHasClut = false;
42 }
43 
~CDVDDemuxSPU()44 CDVDDemuxSPU::~CDVDDemuxSPU()
45 {
46   free(m_spuData.data);
47 }
48 
Reset()49 void CDVDDemuxSPU::Reset()
50 {
51   FlushCurrentPacket();
52 
53   // We can't reset this during playback, cause we don't always
54   // get a new clut from libdvdnav leading to invalid colors
55   // so let's just never reset it. It will only be reset
56   // when VideoPlayer is destructed and constructed
57   // m_bHasClut = false;
58   // memset(m_clut, 0, sizeof(m_clut));
59 }
60 
FlushCurrentPacket()61 void CDVDDemuxSPU::FlushCurrentPacket()
62 {
63   free(m_spuData.data);
64   memset(&m_spuData, 0, sizeof(m_spuData));
65 }
66 
AddData(uint8_t * data,int iSize,double pts)67 CDVDOverlaySpu* CDVDDemuxSPU::AddData(uint8_t* data, int iSize, double pts)
68 {
69   SPUData* pSPUData = &m_spuData;
70 
71   if (pSPUData->iNeededSize > 0 &&
72       (pSPUData->iSize != pSPUData->iNeededSize) &&
73       ((pSPUData->iSize + iSize) > pSPUData->iNeededSize))
74   {
75     DebugLog("corrupt spu data: packet does not fit");
76     m_spuData.iNeededSize = 0;
77     m_spuData.iSize = 0;
78     return NULL;
79   }
80 
81   // check if we are about to start a new packet
82   if (pSPUData->iSize == pSPUData->iNeededSize)
83   {
84     // for now we don't delete the memory associated with m_spuData.data
85     pSPUData->iSize = 0;
86 
87     // check spu data length, only needed / possible in the first spu packet
88     uint16_t length = data[0] << 8 | data[1];
89     if (length == 0)
90     {
91       DebugLog("corrupt spu data: zero packet");
92       m_spuData.iNeededSize = 0;
93       m_spuData.iSize = 0;
94       return NULL;
95     }
96     if (length > iSize) pSPUData->iNeededSize = length;
97     else pSPUData->iNeededSize = iSize;
98 
99     // set presentation time stamp
100     pSPUData->pts = pts;
101   }
102 
103   // allocate data if not already done ( done in blocks off 16384 bytes )
104   // or allocate some more if 16384 bytes is not enough
105   if ((pSPUData->iSize + iSize) > pSPUData->iAllocatedSize)
106   {
107     uint8_t* tmpptr = (uint8_t*)realloc(pSPUData->data, ALIGN(pSPUData->iSize + iSize, 0x4000));
108     if (!tmpptr)
109     {
110       free(pSPUData->data);
111       return NULL;
112     }
113     pSPUData->data = tmpptr;
114   }
115 
116   if(!pSPUData->data)
117     return NULL; // crap realloc failed, this will have leaked some memory due to odd realloc
118 
119   // add new data
120   memcpy(pSPUData->data + pSPUData->iSize, data, iSize);
121   pSPUData->iSize += iSize;
122 
123   if (pSPUData->iNeededSize - pSPUData->iSize == 1) // to make it even
124   {
125     DebugLog("missing 1 byte to complete packet, adding 0xff");
126 
127     pSPUData->data[pSPUData->iSize] = 0xff;
128     pSPUData->iSize++;
129   }
130 
131   if (pSPUData->iSize == pSPUData->iNeededSize)
132   {
133     DebugLog("got complete spu packet\n  length: %i bytes\n  stream: %i\n", pSPUData->iSize);
134 
135     return ParsePacket(pSPUData);
136   }
137 
138   return NULL;
139 }
140 
141 #define CMD_END     0xFF
142 #define FSTA_DSP    0x00
143 #define STA_DSP     0x01
144 #define STP_DSP     0x02
145 #define SET_COLOR   0x03
146 #define SET_CONTR   0x04
147 #define SET_DAREA   0x05
148 #define SET_DSPXA   0x06
149 #define CHG_COLCON  0x07
150 
ParsePacket(SPUData * pSPUData)151 CDVDOverlaySpu* CDVDDemuxSPU::ParsePacket(SPUData* pSPUData)
152 {
153   unsigned int alpha[4];
154   uint8_t* pUnparsedData = NULL;
155 
156   if (pSPUData->iNeededSize != pSPUData->iSize)
157   {
158     DebugLog("GetPacket, packet is incomplete, missing: %i bytes", (pSPUData->iNeededSize - pSPUData->iSize));
159   }
160 
161   if (pSPUData->data[pSPUData->iSize - 1] != 0xff)
162   {
163     DebugLog("GetPacket, missing end of data 0xff");
164   }
165 
166   CDVDOverlaySpu* pSPUInfo = new CDVDOverlaySpu();
167   uint8_t* p = pSPUData->data; // pointer to walk through all data
168 
169   // get data length
170   uint16_t datalength = p[2] << 8 | p[3]; // datalength + 4 control bytes
171 
172   pUnparsedData = pSPUData->data + 4;
173 
174   // if it is set to 0 it means it's a menu overlay by default
175   // this is not what we want too, cause you get strange results on a parse error
176   pSPUInfo->iPTSStartTime = -1;
177 
178   //skip data packet and goto control sequence
179   p += datalength;
180 
181   bool bHasNewDCSQ = true;
182   while (bHasNewDCSQ)
183   {
184     DebugLog("  starting new SP_DCSQT");
185     // p is beginning of first SP_DCSQT now
186     uint16_t delay = p[0] << 8 | p[1];
187     uint16_t next_DCSQ = p[2] << 8 | p[3];
188 
189     //offset within the Sub-Picture Unit to the next SP_DCSQ. If this is the last SP_DCSQ, it points to itself.
190     bHasNewDCSQ = ((pSPUData->data + next_DCSQ) != p);
191     // skip 4 bytes
192     p += 4;
193 
194     while (*p != CMD_END && (unsigned int)(p - pSPUData->data) <= pSPUData->iSize)
195     {
196       switch (*p)
197       {
198       case FSTA_DSP:
199         p++;
200         DebugLog("    GetPacket, FSTA_DSP: Forced Start Display, no arguments");
201         pSPUInfo->iPTSStartTime = pSPUData->pts;
202         pSPUInfo->iPTSStopTime = 0x9000000000000LL;
203         pSPUInfo->bForced = true;
204         // delay is always 0, the VideoPlayer should decide when to display the packet (menu highlight)
205         break;
206       case STA_DSP:
207         {
208           p++;
209           pSPUInfo->iPTSStartTime = pSPUData->pts;
210           pSPUInfo->iPTSStartTime += (double)delay * 1024 * DVD_TIME_BASE / 90000;
211           DebugLog("    GetPacket, STA_DSP: Start Display, delay: %i", ((delay * 1024) / 90000));
212         }
213         break;
214       case STP_DSP:
215         {
216           p++;
217           pSPUInfo->iPTSStopTime = pSPUData->pts;
218           pSPUInfo->iPTSStopTime += (double)delay * 1024 * DVD_TIME_BASE / 90000;
219           DebugLog("    GetPacket, STP_DSP: Stop Display, delay: %i", ((delay * 1024) / 90000));
220         }
221         break;
222       case SET_COLOR:
223         {
224           p++;
225 
226           if (m_bHasClut)
227           {
228             pSPUInfo->bHasColor = true;
229 
230             unsigned int idx[4];
231             // 0, 1, 2, 3
232             idx[0] = (p[0] >> 4) & 0x0f;
233             idx[1] = (p[0]) & 0x0f;
234             idx[2] = (p[1] >> 4) & 0x0f;
235             idx[3] = (p[1]) & 0x0f;
236 
237             for (int i = 0; i < 4 ; i++) // emphasis 1, emphasis 2, pattern, back ground
238             {
239               uint8_t* iColor = m_clut[idx[i]];
240 
241               pSPUInfo->color[3 - i][0] = iColor[0]; // Y
242               pSPUInfo->color[3 - i][1] = iColor[1]; // Cr
243               pSPUInfo->color[3 - i][2] = iColor[2]; // Cb
244             }
245           }
246 
247           DebugLog("    GetPacket, SET_COLOR:");
248           p += 2;
249         }
250         break;
251       case SET_CONTR:  // alpha
252         {
253           p++;
254           // 3, 2, 1, 0
255           alpha[0] = (p[0] >> 4) & 0x0f;
256           alpha[1] = (p[0]) & 0x0f;
257           alpha[2] = (p[1] >> 4) & 0x0f;
258           alpha[3] = (p[1]) & 0x0f;
259 
260           // Ignore blank alpha palette.
261           if (alpha[0] | alpha[1] | alpha[2] | alpha[3])
262           {
263             pSPUInfo->bHasAlpha = true;
264 
265             // 0, 1, 2, 3
266             pSPUInfo->alpha[0] = alpha[3]; //0 // background, should be hidden
267             pSPUInfo->alpha[1] = alpha[2]; //1
268             pSPUInfo->alpha[2] = alpha[1]; //2 // wm button overlay
269             pSPUInfo->alpha[3] = alpha[0]; //3
270           }
271 
272           DebugLog("    GetPacket, SET_CONTR:");
273           p += 2;
274         }
275         break;
276       case SET_DAREA:
277         {
278           p++;
279           pSPUInfo->x = (p[0] << 4) | (p[1] >> 4);
280           pSPUInfo->y = (p[3] << 4) | (p[4] >> 4);
281           pSPUInfo->width = (((p[1] & 0x0f) << 8) | p[2]) - pSPUInfo->x + 1;
282           pSPUInfo->height = (((p[4] & 0x0f) << 8) | p[5]) - pSPUInfo->y + 1;
283           DebugLog("    GetPacket, SET_DAREA: x,y:%i,%i width,height:%i,%i",
284                    pSPUInfo->x, pSPUInfo->y, pSPUInfo->width, pSPUInfo->height);
285           p += 6;
286         }
287         break;
288       case SET_DSPXA:
289         {
290           p++;
291           uint16_t tfaddr = (p[0] << 8 | p[1]); // offset in packet
292           uint16_t bfaddr = (p[2] << 8 | p[3]); // offset in packet
293           pSPUInfo->pTFData = (tfaddr - 4); //pSPUInfo->pData + (tfaddr - 4); // pSPUData->data = packet startaddr - 4
294           pSPUInfo->pBFData = (bfaddr - 4); //pSPUInfo->pData + (bfaddr - 4); // pSPUData->data = packet startaddr - 4
295           p += 4;
296           DebugLog("    GetPacket, SET_DSPXA: tf: %i bf: %i ", tfaddr, bfaddr);
297         }
298         break;
299       case CHG_COLCON:
300         {
301           p++;
302           uint16_t paramlength = p[0] << 8 | p[1];
303           DebugLog("GetPacket, CHG_COLCON, skippin %i bytes", paramlength);
304           p += paramlength;
305         }
306         break;
307 
308       default:
309         DebugLog("GetPacket, error parsing control sequence");
310         delete pSPUInfo;
311         return NULL;
312         break;
313       }
314     }
315     DebugLog("  end off SP_DCSQT");
316     if (*p == CMD_END) p++;
317     else
318     {
319       DebugLog("GetPacket, end off SP_DCSQT, but did not found 0xff (CMD_END)");
320     }
321   }
322 
323   // parse the rle.
324   // this should be changed so it get's converted to a yuv overlay
325   return ParseRLE(pSPUInfo, pUnparsedData);
326 }
327 
328 /*****************************************************************************
329  * AddNibble: read a nibble from a source packet and add it to our integer.
330  *****************************************************************************/
AddNibble(unsigned int i_code,uint8_t * p_src,unsigned int * pi_index)331 inline unsigned int AddNibble( unsigned int i_code, uint8_t* p_src, unsigned int* pi_index )
332 {
333   if ( *pi_index & 0x1 )
334   {
335     return ( i_code << 4 | ( p_src[(*pi_index)++ >> 1] & 0xf ) );
336   }
337   else
338   {
339     return ( i_code << 4 | p_src[(*pi_index)++ >> 1] >> 4 );
340   }
341 }
342 
343 /*****************************************************************************
344  * ParseRLE: parse the RLE part of the subtitle
345  *****************************************************************************
346  * This part parses the subtitle graphical data and stores it in a more
347  * convenient structure for later decoding. For more information on the
348  * subtitles format, see http://sam.zoy.org/doc/dvd/subtitles/index.html
349  *****************************************************************************/
ParseRLE(CDVDOverlaySpu * pSPU,uint8_t * pUnparsedData)350 CDVDOverlaySpu* CDVDDemuxSPU::ParseRLE(CDVDOverlaySpu* pSPU, uint8_t* pUnparsedData)
351 {
352   uint8_t* p_src = pUnparsedData;
353 
354   unsigned int i_code = 0;
355 
356   unsigned int i_width = pSPU->width;
357   unsigned int i_height = pSPU->height;
358   unsigned int i_x, i_y;
359 
360   // allocate a buffer for the result
361   uint16_t* p_dest = (uint16_t*)pSPU->result;
362 
363   /* The subtitles are interlaced, we need two offsets */
364   unsigned int i_id = 0;                   /* Start on the even SPU layer */
365   unsigned int pi_table[2];
366 
367   /* Colormap statistics */
368   int i_border = -1;
369   int stats[4]; stats[0] = stats[1] = stats[2] = stats[3] = 0;
370 
371   pi_table[ 0 ] = pSPU->pTFData << 1;
372   pi_table[ 1 ] = pSPU->pBFData << 1;
373 
374   for ( i_y = 0 ; i_y < i_height ; i_y++ )
375   {
376     unsigned int *pi_offset = pi_table + i_id;
377 
378     for ( i_x = 0 ; i_x < i_width ; i_x += i_code >> 2 )
379     {
380       i_code = AddNibble( 0, p_src, pi_offset );
381 
382       if ( i_code < 0x04 )
383       {
384         i_code = AddNibble( i_code, p_src, pi_offset );
385 
386         if ( i_code < 0x10 )
387         {
388           i_code = AddNibble( i_code, p_src, pi_offset );
389 
390           if ( i_code < 0x040 )
391           {
392             i_code = AddNibble( i_code, p_src, pi_offset );
393 
394             if ( i_code < 0x0100 )
395             {
396               /* If the 14 first bits are set to 0, then it's a
397                * new line. We emulate it. */
398               if ( i_code < 0x0004 )
399               {
400                 i_code |= ( i_width - i_x ) << 2;
401               }
402               else
403               {
404                 /* We have a boo boo ! */
405                 CLog::Log(LOGERROR, "ParseRLE: unknown RLE code 0x%.4x", i_code);
406                 pSPU->Release();
407                 return NULL;
408               }
409             }
410           }
411         }
412       }
413 
414       if ( ( (i_code >> 2) + i_x + i_y * i_width ) > i_height * i_width )
415       {
416         CLog::Log(LOGERROR, "ParseRLE: out of bounds, %i at (%i,%i) is out of %ix%i",
417                  i_code >> 2, i_x, i_y, i_width, i_height );
418         pSPU->Release();
419         return NULL;
420       }
421 
422       // keep trace of all occurring pixels, even keeping the background in mind
423       stats[i_code & 0x3] += i_code >> 2;
424 
425       // count the number of pixels for every occurring parts, without background
426       if (pSPU->alpha[i_code & 0x3] != 0x00)
427       {
428         // the last non background pixel is probably the border color
429         i_border = i_code & 0x3;
430         stats[i_border] += i_code >> 2;
431       }
432 
433       /* Check we aren't overwriting our data range
434          This occurs on "The Triplets of BelleVille" region 4 disk (NTSC)"
435          where we use around 96k rather than 64k + 20bytes */
436       if ((uint8_t *)p_dest >= pSPU->result + sizeof(pSPU->result))
437       {
438         CLog::Log(LOGERROR, "ParseRLE: Overrunning our data range.  Need %li bytes", (long)((uint8_t *)p_dest - pSPU->result));
439         pSPU->Release();
440         return NULL;
441       }
442       *p_dest++ = i_code;
443     }
444 
445     /* Check that we didn't go too far */
446     if ( i_x > i_width )
447     {
448       CLog::Log(LOGERROR, "ParseRLE: i_x overflowed, %i > %i", i_x, i_width );
449       pSPU->Release();
450       return NULL;
451     }
452 
453     /* Byte-align the stream */
454     if ( *pi_offset & 0x1 )
455     {
456       (*pi_offset)++;
457     }
458 
459     /* Swap fields */
460     i_id = ~i_id & 0x1;
461   }
462 
463   /* We shouldn't get any padding bytes */
464   if ( i_y < i_height )
465   {
466     DebugLog("ParseRLE: padding bytes found in RLE sequence" );
467     DebugLog("ParseRLE: send mail to <sam@zoy.org> if you want to help debugging this" );
468 
469     /* Skip them just in case */
470     while ( i_y < i_height )
471     {
472       /* Check we aren't overwriting our data range
473          This occurs on "The Triplets of BelleVille" region 4 disk (NTSC)"
474          where we use around 96k rather than 64k + 20bytes */
475       if ((uint8_t *)p_dest >= pSPU->result + sizeof(pSPU->result))
476       {
477         CLog::Log(LOGERROR, "ParseRLE: Overrunning our data range.  Need %li bytes", (long)((uint8_t *)p_dest - pSPU->result));
478         pSPU->Release();
479         return NULL;
480       }
481       *p_dest++ = i_width << 2;
482       i_y++;
483     }
484 
485     pSPU->Release();
486     return NULL;
487   }
488 
489   DebugLog("ParseRLE: valid subtitle, size: %ix%i, position: %i,%i",
490            pSPU->width, pSPU->height, pSPU->x, pSPU->y );
491 
492   // forced spu's (menu overlays) retrieve their alpha/color information from InputStreamNavigator::GetCurrentButtonInfo
493   // also they may contain completely covering data wich is supposed to be hidden normally
494   // since whole spu is drawn, if this is done for forced, that may be displayed
495   // so we must trust what is given
496   if( !pSPU->bForced )
497   {
498     // Handle color if no palette was found.
499     // we only set it if there is a valid i_border color
500     if (!pSPU->bHasColor)
501     {
502       CLog::Log(LOGINFO, "%s - no color palette found, using default", __FUNCTION__);
503       FindSubtitleColor(i_border, stats, pSPU);
504     }
505 
506     // check alpha values, for non forced spu's we use a default value
507     if (pSPU->bHasAlpha)
508     {
509       // check alpha values
510       // the array stats represents the nr of pixels for each color channel
511       // thus if there are no pixels to display, we assume the alphas are incorrect.
512       if (!CanDisplayWithAlphas(pSPU->alpha, stats))
513       {
514         CLog::Log(LOGINFO, "%s - no  matching color and alpha found, resetting alpha", __FUNCTION__);
515 
516         pSPU->alpha[0] = 0x00; // back ground
517         pSPU->alpha[1] = 0x0f;
518         pSPU->alpha[2] = 0x0f;
519         pSPU->alpha[3] = 0x0f;
520       }
521     }
522     else
523     {
524       CLog::Log(LOGINFO, "%s - ignoring blank alpha palette, using default", __FUNCTION__);
525 
526       pSPU->alpha[0] = 0x00; // back ground
527       pSPU->alpha[1] = 0x0f;
528       pSPU->alpha[2] = 0x0f;
529       pSPU->alpha[3] = 0x0f;
530     }
531 
532   }
533 
534   return pSPU;
535 }
536 
FindSubtitleColor(int last_color,int stats[4],CDVDOverlaySpu * pSPU)537 void CDVDDemuxSPU::FindSubtitleColor(int last_color, int stats[4], CDVDOverlaySpu* pSPU)
538 {
539   const int COLOR_INNER = 0;
540   const int COLOR_SHADE = 1;
541   const int COLOR_BORDER = 2;
542 
543   //uint8_t custom_subtitle_color[4][3] = { // blue, yellow and something else (xine)
544   //  { 0x80, 0x90, 0x80 }, // inner color
545   //  { 0x00, 0x90, 0x00 }, // shade color
546   //  { 0x00, 0x90, 0xff }  // border color
547   //};
548 
549   uint8_t custom_subtitle_color[4][3] = { // inner color white, gray shading and a black border
550     { 0xff, 0x80, 0x80 }, // inner color, white
551     { 0x80, 0x80, 0x80 }, // shade color, gray
552     { 0x00, 0x80, 0x80 }  // border color, black
553   };
554 
555   //uint8_t custom_subtitle_color[4][3] = { // completely white and a black border
556   //  { 0xff, 0x80, 0x80 }, // inner color, white
557   //  { 0xff, 0x80, 0x80 }, // shade color, white
558   //  { 0x00, 0x80, 0x80 }  // border color, black
559   //};
560 
561 
562   int nrOfUsedColors = 0;
563   for (int alpha : pSPU->alpha)
564   {
565     if (alpha > 0) nrOfUsedColors++;
566   }
567 
568   if (nrOfUsedColors == 0)
569   {
570     // nothing todo
571     DebugLog("FindSubtitleColor: all 4 alpha channels are 0, nothing todo");
572   }
573   else if (nrOfUsedColors == 1)
574   {
575     // only one color is used, probably the inner color
576     for (int i = 0; i < 4; i++) // find the position that is used
577     {
578       if (pSPU->alpha[i] > 0)
579       {
580         pSPU->color[i][0] = custom_subtitle_color[COLOR_INNER][0]; // Y
581         pSPU->color[i][1] = custom_subtitle_color[COLOR_INNER][1]; // Cr ?
582         pSPU->color[i][2] = custom_subtitle_color[COLOR_INNER][2]; // Cb ?
583         return;
584       }
585     }
586 
587   }
588   else
589   {
590     // old code
591 
592     if (last_color >= 0 && last_color < 4)
593     {
594       int i, i_inner = -1, i_shade = -1;
595       // Set the border color, the last color is probably the border color
596       pSPU->color[last_color][0] = custom_subtitle_color[COLOR_BORDER][0];
597       pSPU->color[last_color][1] = custom_subtitle_color[COLOR_BORDER][1];
598       pSPU->color[last_color][2] = custom_subtitle_color[COLOR_BORDER][2];
599       stats[last_color] = 0;
600 
601     // find the inner colors
602     for ( i = 0 ; i < 4 && i_inner == -1 ; i++ )
603     {
604       if ( stats[i] )
605       {
606         i_inner = i;
607       }
608     }
609 
610     // try to find the shade color
611     for ( ; i < 4 && i_shade == -1 ; i++)
612     {
613       if ( stats[i] )
614       {
615         if ( stats[i] > stats[i_inner] )
616         {
617           i_shade = i_inner;
618           i_inner = i;
619         }
620         else
621         {
622           i_shade = i;
623         }
624       }
625     }
626 
627     /* Set the inner color */
628     if ( i_inner != -1 )
629     {
630       // white color
631         pSPU->color[i_inner][0] = custom_subtitle_color[COLOR_INNER][0]; // Y
632         pSPU->color[i_inner][1] = custom_subtitle_color[COLOR_INNER][1]; // Cr ?
633         pSPU->color[i_inner][2] = custom_subtitle_color[COLOR_INNER][2]; // Cb ?
634     }
635 
636     /* Set the anti-aliasing color */
637     if ( i_shade != -1 )
638     {
639       // gray
640         pSPU->color[i_shade][0] = custom_subtitle_color[COLOR_SHADE][0];
641         pSPU->color[i_shade][1] = custom_subtitle_color[COLOR_SHADE][1];
642         pSPU->color[i_shade][2] = custom_subtitle_color[COLOR_SHADE][2];
643     }
644 
645       DebugLog("ParseRLE: using custom palette (border %i, inner %i, shade %i)", last_color, i_inner, i_shade);
646     }
647   }
648 }
649 
CanDisplayWithAlphas(int a[4],int stats[4])650 bool CDVDDemuxSPU::CanDisplayWithAlphas(int a[4], int stats[4])
651 {
652   return(
653     a[0] * stats[0] > 0 ||
654     a[1] * stats[1] > 0 ||
655     a[2] * stats[2] > 0 ||
656     a[3] * stats[3] > 0);
657 }
658