将图形保存到EEPROM,通过过滤器重复0x00和0xFF来压缩图形以节省空间

时间:2017-10-17 01:34:32

标签: c++ c compression avr eeprom

作为固件的一部分,我想将图形或图形保存到MCU的EEPROM中。空间不大,1K,但它可以节省一些程序空间。是的,你可以分离图形的字形以节省空间,但它不容易管理,你需要更多的代码来正确显示它。

大多数单色GUI图形不会完全填满屏幕并包含大量空白空间或重复像素。图像已经被压缩,一个字节中的每个位代表8个像素。

我在128x32像素的小显示屏上显示图形。显示并擦除不相关的部件,效果非常好,效率很高。

所以我想出了过滤那些重复的想法,将其压缩一点。成功的是,这样的位图(见下文)是496个字节,并且用我的方法“压缩”了401个字节。

enter image description here

这听起来并不多,但是总体尺寸减少了20%,当只有1K存储空间时真的很棒。

字节数组示例:

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个像素,只是想稍微减少一点(证明在字节溢出时有错误)。

2 个答案:

答案 0 :(得分:2)

第一次通过循环时,bFFOverflowbZeroOverflow无需初始化即可访问。

但主要问题是,在记录255 0或0xFF字节后,如果有更多,则将计数设置为1。但是,这应该为零,因为您在计算了该字节的第255个副本后检测到溢出。

所以始终将bFFOverflowbZeroOverflow设置为0来写出计数。

答案 1 :(得分:0)

经过一段时间的睡眠后,我用更好的结果和更少的代码重做它,我太过复杂了。

我用它得到了令人印象深刻的结果,并考虑用一种检查方法来改进它,以找到最好的压缩效果。选择重复次数最多的字节并将其记录到EEPROM文件中。

无论如何,这是我的代码,与第一个相比要好得多,也许它可以帮助别人。这是保存一些字节的非常轻量级的解决方案。

例如,分辨率为128x32像素的空白屏幕或完整填充屏幕仅产生9个字节,一半只有17个字节。我之前在问题中显示的屏幕编译了#39;只有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边界检查,但现在它工作正常。