Сохранение графики в EEPROM, сжатие графики фильтром, повторяющим 0x00 и 0xFF, для экономии места

В рамках прошивки я хочу сохранить графику или графику в EEPROM микроконтроллера. Места не так много, 1K, однако это может сэкономить место для программы. И да, вы можете разделить глифы на графике, чтобы сэкономить место, однако это непросто сделать, и вам нужно больше кода, чтобы отобразить его правильно.

Большая часть монохромной графики GUI не заполняет экран полностью и содержит пустое пространство или повторяющиеся пиксели. Изображения уже сжаты, каждый бит в байте соответствует 8 пикселям.

Я показываю графику на крошечном дисплее размером 128x32 пикселя. Отобразите его и сотрите ненужные части, работает отлично и эффективно.

Так что мне пришла в голову идея отфильтровать эти повторы, немного их сжать. При успешном выполнении растровое изображение, подобное этому (см. Ниже), имеет размер 496 байт и «сжимается» с помощью моего метода меньше 401 байта.


введите здесь описание изображения


Это звучит не очень, однако общий размер уменьшается на 20%, что действительно здорово, когда доступно только 1 КБ.

Пример массива байтов:

PROGMEM const uint8_t TEP_DISPLAY [] = { /* 496 bytes */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00,
0x06, 0x00, 0x80, 0x90, 0x00, 0x3E, 0x01, 0x80, 0x03, 0xC0, 0x01, 0x80, 0x00, 0x47, 0x0F, 0xFE,
0x17, 0x01, 0xC0, 0x90, 0x00, 0x30, 0x00, 0x00, 0x03, 0x60, 0x01, 0x80, 0x01, 0x87, 0x10, 0x02,
0x30, 0x83, 0xE3, 0xFC, 0x00, 0x61, 0xE7, 0x39, 0xB6, 0x6F, 0x0F, 0x00, 0x03, 0x07, 0x36, 0xDA,
0x7F, 0xF0, 0x83, 0xFC, 0x7C, 0x7D, 0xB3, 0x6D, 0xB6, 0x61, 0x9B, 0x1F, 0x03, 0x87, 0x36, 0xDA,
0x30, 0x43, 0xE1, 0xF8, 0x00, 0x61, 0xB3, 0x6D, 0xA7, 0xCF, 0xB3, 0x00, 0x01, 0x80, 0x36, 0xDA,
0x13, 0x81, 0xC0, 0x60, 0x00, 0xC3, 0x66, 0x6D, 0xCC, 0x1B, 0x36, 0x00, 0x01, 0x07, 0x10, 0x02,
0x03, 0x00, 0x80, 0x60, 0x00, 0xFB, 0x66, 0x39, 0x8C, 0x0F, 0x1E, 0x00, 0x02, 0x07, 0x0F, 0xFE,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA2, 0xD5, 0x54,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x02,
0x00, 0xC0, 0x22, 0x00, 0x08, 0x00, 0x02, 0x20, 0x00, 0x82, 0x48, 0x20, 0x00, 0x08, 0x00, 0x00,
0x40, 0xC0, 0x01, 0xE0, 0x00, 0x01, 0xC0, 0x1E, 0x00, 0x01, 0x50, 0x00, 0xFE, 0x00, 0x0C, 0x02,
0x00, 0xC0, 0x20, 0x10, 0x08, 0x07, 0xC2, 0x01, 0x00, 0x80, 0x00, 0x21, 0x01, 0x08, 0x0E, 0x00,
0x4F, 0xFC, 0x00, 0xFE, 0x00, 0x0F, 0x40, 0x3F, 0xF8, 0x03, 0xF8, 0x03, 0x01, 0x80, 0x0B, 0x02,
0x1C, 0xC2, 0x21, 0x11, 0x08, 0x1C, 0x42, 0x40, 0x04, 0x84, 0x04, 0x21, 0x11, 0x08, 0x69, 0x80,
0x59, 0xE2, 0x01, 0x11, 0x00, 0x18, 0x40, 0x55, 0x54, 0x05, 0x54, 0x03, 0x39, 0x80, 0x3B, 0x02,
0x12, 0xD2, 0x21, 0x11, 0x08, 0x10, 0x42, 0x40, 0x04, 0x84, 0x04, 0x21, 0x7D, 0x08, 0x1E, 0x00,
0x54, 0xCA, 0x01, 0x83, 0x00, 0x10, 0x40, 0x55, 0x54, 0x05, 0x54, 0x03, 0x11, 0x80, 0x3E, 0x02,
0x12, 0x12, 0x21, 0x01, 0x08, 0x11, 0xC2, 0x40, 0x04, 0x84, 0x04, 0x21, 0x11, 0x08, 0x6B, 0x00,
0x51, 0xE2, 0x01, 0x01, 0x00, 0x13, 0xC0, 0x47, 0xC4, 0x04, 0x44, 0x01, 0x11, 0x00, 0x09, 0x82,
0x10, 0x02, 0x21, 0x01, 0x08, 0x71, 0x82, 0x40, 0x04, 0x84, 0x04, 0x23, 0x01, 0x88, 0x0B, 0x00,
0x4F, 0xFC, 0x01, 0xFF, 0x00, 0xF0, 0x00, 0x3F, 0xF8, 0x05, 0x54, 0x01, 0x01, 0x00, 0x0E, 0x02,
0x0F, 0xFC, 0x20, 0xFE, 0x08, 0x60, 0x02, 0x1F, 0xF0, 0x84, 0x04, 0x20, 0xFE, 0x08, 0x0C, 0x00,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02
};

Тем не менее, есть одна проблема, и я думаю, что это небольшая ошибка в коде, и я не могу ее обнаружить (потому что потратьте пару дней на это, чтобы подумать, как сделать ее немного меньше). Может быть, есть кто-то, кто может указать мне правильное направление для решения проблемы.

Проблема

Проблема возникает, когда имеется много сходств, более 255 повторений одного и того же, например, много повторяющихся строк 0xFF или повторяющихся пустых пространств 0x00. В моем коде я принимаю некоторые меры предосторожности, чтобы избежать переполнения байтов, однако это не удается (и теперь не могу понять, почему). Что я пытаюсь сделать, так это когда происходит переполнение, записываю его и начинаю заново со счета. Я не могу понять, что это проблема функции чтения или только функции записи.

Это схема хранения

At start address:
-----------------
<byte width>
<byte heigth>
<uint16 dataSize>
<data>
  <if data=0xFF>
    <0xFF>
    <repeat count>
   </if>
   <if data=0x00>
     <0x00>
     <repeat count>
   </if>
 <else data>
</data>

Вот мой код:

uint16_t TOLEDdisplay::writeToEeprom( uint16_t iAddress )
{
  if( width == 0 || height == 0 || cacheSize == 0 )
   { return 0; }

  uint8_t   iZeros    = 0;
  uint8_t   iFFs      = 0;
  bool      bIsZero   = false;  
  bool      bIsFF     = false;
  bool      bZeroOverflow = false;
  bool      bFFOverflow = false;

  uint16_t  iBits     = 0;
  uint8_t*  pByteSize = (uint8_t*)&iBits;
  uint8_t   iZeroCount = 0; // empty stripes , same pixels in a row
  uint8_t   iFFCount   = 0; // filled stripes, same pixels in a row 


  // Write screen bounds, when read it back with readFromEeprom,
  // this bounds must match with the current screen bounds.
  EEPROM.write( iAddress++, width );
  EEPROM.write( iAddress++, height );

   // Reserve two bytes for stream size
  uint16_t iSizeAddress = iAddress++;
  ++iAddress;

   // Write the cache content to the EEPROM
  uint16_t  i = 0;
  while( i < cacheSize )
  {
    iBits   = getCacheRawBits( i );
    //iBits   = displayCache[ i ];
    bIsFF   = ( iBits == 0xFF );
    bIsZero = ( iBits == 0x00 ); 

    if( bIsFF && !bFFOverflow )
     { ++iFFs; } 
    bFFOverflow = (iFFs == 0xFF);

    if( bIsZero && !bZeroOverflow )
     { ++iZeros; }
    bZeroOverflow = (iZeros == 0xFF);

    if( (!bIsFF && !bIsZero) || bFFOverflow || bZeroOverflow )
    {
           if( (!bIsFF && iFFs > 0) || bFFOverflow )
           { 
              // Read function knows if there is a 0xFF, amount of 0xFF
              // will be follow.
             EEPROM.write( iAddress++, 0xFF ); 
              // Write the amount of FF's
             EEPROM.write( iAddress++, iFFs ); 

             iFFCount+=iFFs;

              // If there is no byte 'overflow' iFFs = 0, otherwise it is 1
             iFFs = (uint8_t)bIsFF;
           }  

           if( (!bIsZero && iZeros > 0) || bZeroOverflow )
           { 
              // Read function knows if there is a zero, amount of zeros
              // will be follow.
             EEPROM.write( iAddress++, 0 ); 
              // Write the amount of zero's
             EEPROM.write( iAddress++, iZeros ); 

             iZeroCount+=iZeros;

              // If there is no byte 'overflow' iZeros = 0, otherwise it is 1
             iZeros = (uint8_t)bIsZero;
           }  

            // Avoid confusion writing a FF or zero 
           if( !bIsFF && !bIsZero  )           
            { EEPROM.write( iAddress++, iBits ); }
    }

    ++i;
  }

   // Calculate stream size
  iBits=iAddress-iSizeAddress-1;

   // Write size of stream
  EEPROM.write( iSizeAddress  , *pByteSize++ );
  EEPROM.write( iSizeAddress+1, *pByteSize );

  Serial.print( "Zeros found: " );
  Serial.println( iZeroCount );
  Serial.print( "FF found: " );
  Serial.println( iFFCount );
  Serial.print( "SIZE: " );
  Serial.println( iBits );

  // return bytes written
  return iBits+2;
}

bool TOLEDdisplay::readFromEeprom( uint16_t iAddress )
{
  uint8_t  bits    = 0;
  uint16_t i       = 0; 
  uint8_t* pI      = (uint8_t*)&i;
  uint8_t  iZeros  = 0;
  uint8_t  iFFs    = 0;

  uint8_t  iWidth  = EEPROM.read( iAddress++ );
  uint8_t  iHeight = EEPROM.read( iAddress++ );

   // Read stream size, read two bytes
  *pI = EEPROM.read( iAddress++ );
  *pI++;
  *pI = EEPROM.read( iAddress++ );

   // Clear the screen
  clear();

  Serial.print( "Size: " );
  Serial.println( i );
  Serial.print( "Width: " );
  Serial.println( iWidth );
  Serial.print( "Height: " );
  Serial.println( iHeight );

   // If an error (no image on EEPROM address) or screen bounds 
   // doesn't match, skip to continue
  if( i == 0 || iWidth != width || iHeight != height )
  {  
    update( true );
    return false; 
  }

  uint16_t iCacheAddress = 0; 

  while( i-- )
  {
    do { 
     if( iFFs == 0 && iZeros == 0 )
     {
        bits = EEPROM.read( iAddress++ );    

        if( bits == 0xFF )
         { 
           // read amount of FF's minus this one
           iFFs = EEPROM.read( iAddress++ )-1; 
           Serial.print( "iFFs: ");
           Serial.println( iFFs );
         }
        else if( bits == 0x00 )
             { 
               // read amount of zeros minus this one
               iZeros = EEPROM.read( iAddress++ )-1; 
               Serial.print( "iZeros: ");
               Serial.println( iZeros );
             }
     }   
     else { 
            if( iFFs > 0 )
            {
              --iFFs; 
              bits = 0xFF;
            }
            else if( iZeros > 0 )
                 {
                   --iZeros; 
                   bits = 0x00;
                 }  
          }


      setCacheRawBits( iCacheAddress, bits );
      ++iCacheAddress;
    }
    while( iFFs == 0 && iZeros == 0 );
  }

  update( true );
  return true;
}

Есть идеи?


ПРИМЕЧАНИЕ.

Я не хочу использовать какой-либо дорогостоящий метод сжатия, 96% пространства программы уже используется, и мой метод работает нормально, но с некоторой ошибкой, и мне нужно знать ошибку, альтернативного метода сжатия нет. Он уже имеет некоторое сжатие, биты в байте представляют 8 пикселей и просто хотят немного его уменьшить (однако доказано с ошибкой при переполнении байта).


person Codebeat    schedule 17.10.2017    source источник
comment
Плохая прическа, ребята? -1 объясни почему, когда минусуешь.   -  person Codebeat    schedule 17.10.2017


Ответы (2)


В первый раз через цикл доступ к bFFOverflow и bZeroOverflow осуществляется без инициализации.

Однако основная проблема заключается в том, что после записи 255 байтов 0 или 0xFF вы устанавливаете счетчик равным 1, если их больше. Однако это значение должно быть равно нулю, так как вы обнаружите переполнение после того, как подсчитаете 255-ю копию этого байта.

Поэтому всегда устанавливайте bFFOverflow и bZeroOverflow на 0, записывая счетчики.

person 1201ProgramAlarm    schedule 17.10.2017
comment
Привет, спасибо за быстрый ответ. Вы не правы, логические значения инициализируются перед проверкой условия. И я считаю, что вы также не правы, когда дело доходит до счета. Когда происходит переполнение, остается еще одно, если оно было одним из условий 0x00 или 0xFF. Тем не менее, взгляните на него завтра ;-) Спасибо - person Codebeat; 17.10.2017
comment
просто проверьте, измените логические значения в части инициализации на false, это не имеет никакого смысла. - person Codebeat; 17.10.2017

После некоторого сна я переделал это с гораздо лучшими результатами и меньшим количеством кода, я слишком усложнил эту вещь.

Я получил с ним впечатляющие результаты и думаю о том, чтобы усовершенствовать его с помощью метода проверки, чтобы найти наилучшее «сжатие», выбрав байты, которые повторяются больше всего, и записать их в «файл» EEPROM.

Во всяком случае, это мой код, намного лучше первого, может быть, он может помочь другим. Это очень легкое решение для экономии нескольких байтов.

Например, пустой экран или полностью заполненный экран с разрешением 128x32 пикселей дает только 9 байт, половина на половину только 17 байт. Экран, который я показал в вопросе до того, как «компилируется» всего до 405 байтов, экономия около 100 байтов.


Вот мой обновленный код:

uint8_t TOLEDdisplay::getCacheRawBits( uint16_t iAddress )
{
  if( iAddress < cacheSize )
   { return displayCache[ iAddress ]; }

  return 0x00;
}

bool TOLEDdisplay::setCacheRawBits( uint16_t iAddress, uint8_t iBitByte )
{
  if( iAddress < cacheSize )
  { 
     displayCache[ iAddress ]=iBitByte; 
     return true;
  }

  return false;
}

uint16_t TOLEDdisplay::writeToEeprom( uint16_t iAddress )
{
  if( cacheSize == 0 || width == 0 || height == 0 )
   { return 0; }

  uint8_t   iBits;              // Pixel * 8 = byte
  uint8_t   iFoundBits;         // 'Type' of detected 
  uint16_t  iByteSize = 0;      // Total byte size of stream
  uint8_t   iCount    = 0;      // Count of repeats found 
  bool      bSame;              // Boolean to evaluate repeats   

  // Write screen bounds, when read it back with readFromEeprom,
  // these bounds need to match with current screen bounds.
  EEPROM.write( iAddress++, width );
  EEPROM.write( iAddress++, height );

   // Reserve two bytes for stream size
  uint16_t iSizeAddress = iAddress;
  iAddress+=2;

   // Write the cache content to the EEPROM
  uint16_t  i = 0;
  while( i < cacheSize )
  {
     // Get a byte with bits
    iBits = getCacheRawBits( i );
    ++i;

     // Find repeating lines or empty lines 
    if( iBits == 0xFF || iBits == 0x00 )
    {
      iFoundBits = iBits;  // Set found bits to detect changes
      bSame      = true;   // Set to true to able to start loop
      iCount=1;            // Count this found one

       // Loop to find duplicates
      while( bSame && ( iCount < 0xFF ) && ( i < cacheSize )) 
      { 
          iBits = getCacheRawBits( i );   // Get next byte with bits
          bSame = (iBits == iFoundBits);  // Determine is repeat, the same
          iCount+=bSame;                  // Increment count when same is found
          i+=bSame;
      }       

       // Finally write result to EEPROM
      EEPROM.write( iAddress++, iFoundBits ); // type
       // Write the amount 
      EEPROM.write( iAddress++, iCount ); // count

      // Goto main loop and find next if any 
    }
   else { 
           // Write found value normally to EEPROM
          EEPROM.write( iAddress++, iBits ); 
        } 
  }

  // Final EOF address is one pos back
  --iAddress; 

   // Calculate stream size
  iByteSize=iAddress-iSizeAddress;
  uint8_t*  pByteSize = (uint8_t*)&iByteSize;

   // Write size of stream
  EEPROM.write( iSizeAddress  , *pByteSize++ );
  EEPROM.write( iSizeAddress+1, *pByteSize );

  // return bytes written including width and height bytes (+2 bytes)
  return iByteSize+2;
}

bool TOLEDdisplay::readFromEeprom( uint16_t iAddress )
{
  uint8_t  iBits;
  uint8_t  iRepeats;   
  uint16_t i        = 0; 
  uint8_t* pI       = (uint8_t*)&i;

  uint8_t  iWidth  = EEPROM.read( iAddress++ );
  uint8_t  iHeight = EEPROM.read( iAddress++ );

   // Read stream size, read two bytes
  *pI = EEPROM.read( iAddress++ );
  *pI++;
  *pI = EEPROM.read( iAddress++ );

   // Clear the screen
  clear();

   // If an error (no image on EEPROM address) or screen bounds 
   // doesn't match, skip to continue
  if( i == 0 || iWidth != width || iHeight != height )
  {  

    update( true ); // Set screen to blank
    return false; 
  }

  uint16_t iCacheAddress = 0; 

  while( i-- )
  {
      // Get a byte with bits
     iBits = EEPROM.read( iAddress++ );    

      // Explode repeats if detected
     if( iBits == 0xFF || iBits == 0x00 )
     { 
        // read amount of repeats
       iRepeats = EEPROM.read( iAddress++ ); 

        // Explode it into cache
       while( iRepeats-- )
        { setCacheRawBits( iCacheAddress++, iBits ); }
     }   
     else { 
             // Put value normally into cache
            setCacheRawBits( iCacheAddress++, iBits ); 
          }
  }

   // Done, update the screen
  update( true );

   // Return success
  return true;
} 

Возможно, мне нужно добавить некоторые проверки границ EEPROM, но пока все работает нормально.

person Codebeat    schedule 18.10.2017