Parsing images from SD


#1

Guys! Is here anybody who successfully got images from SD card?
I’m trying to get images from SD card and send it to addressable LED strip… So i’m trying to parse GIF files from SD into array. To send later this array into leds.

And i’m stuck.
All that i’ve found is one C program, which get one frame GIF and send it’s to display. I’ve rewriten this into C++ class, and it’s not fit memory :frowning: My local compiler tells, that program will not fit RAM, and doesn’t compile it. Maybe someone already have script to parse GIF from SD? Or maybe there is another formats to send animation to leds from SD, wich will better fit this hardware…?


#2

Not having your code, it’s just a guess, but I think your promblem is not the logic to parse the img, but the RAM you need to buffer the img.
Maybe you’d need to load smaller chunks of the img at a time, push this chunk out to your display, to free the RAM and move on to the next chunk - or you buffer your parsed data somewhere else and push the data out from there.

Fact is: RAM is not thick on the Core (I think max. 20KB of which up to 14K might by taken by :spark: FW, leaving you with 6K to play with).


#3

ekbduffy, ScurffR is correct in that seeing your code would help a lot. He is also correct in saying that there is about 6KB of RAM available for programs so managing how you pull data from the SD is crucial.

I found an arduino sketch that pre-processes .bmp images in rows and writes the data back to the SD. It then reads the pre-processed SD file and sends stips to a neopixel array. That approach keeps the memory requirement low since most of the work is done in the SD buffers. So there are many ways of doing this and finding the one that works is the trick. :smile:


#4

No, it’s not trying to load all image into RAM, there’s just dictionary, for decoding GIF file…

This is the error, that gives local compiler:

c:/program files (x86)/gnu tools arm embedded/4.8 2013q4/bin/../lib/gcc/arm-none
-eabi/4.8.3/../../../../arm-none-eabi/bin/ld.exe: region `RAM' overflowed by 808
 bytes
collect2.exe: error: ld returned 1 exit status
make: *** [core-firmware.elf] Error 1

gif.h:

#ifndef __GIF_H__
#define __GIF_H__

#include <SD.h>
#include "ws2812.h"
#include "spark_wiring_usbserial.h"
typedef struct{
	unsigned short	SCREEN_WIDTH;
	unsigned short	SCREEN_HEIGHT;
	unsigned char		BYTE_5;
	unsigned char		BACKGROUND;
	unsigned char		ASPECT_RATIO;
} SCREEN_DESC;

//Struktura do przechowywania deskryptora obrazu
typedef struct{
	unsigned short	LEFT;
	unsigned short	UP;
	unsigned short	IMAGE_WIDTH;
	unsigned short	IMAGE_HEIGHT;
	unsigned char		BYTE_9;
} IMAGE_DESC;

//Struktura triady (R,G,B)
typedef struct{
	unsigned char R;
	unsigned char G;
	unsigned char B;
} COLOR;

//Struktura pliku GIF - deskryptory
typedef struct{
	SCREEN_DESC					screenDesc;
	IMAGE_DESC					imageDesc;
} GIF_IMAGE;

//Struktura elementu slownika
typedef struct{
	unsigned short code;
	unsigned short prevCode;
	unsigned short nextCode;
} DICTIONARY_ENTRY;


typedef unsigned short      WORD;






class GIF
{
private:
	unsigned char BUFFER[16];
	unsigned char tempCHAR2[3];
	unsigned short PrimaryCodeSize, PrimaryDictSize;
	unsigned short currentCodeSize, currentDictSize;

	unsigned short code, oldcode = 0;
	unsigned short code1, code2;
	unsigned short code_CLEAR, code_END;

	COLOR g_GlobalColorTable[256];

#define MAX_DICT_SIZE	100
	DICTIONARY_ENTRY Dictionary[MAX_DICT_SIZE];	//max. 4096
	unsigned char bitsRemaining;
	unsigned short getNextCode(void);
	int checkSignature(char*);
	File g_fileObject;
	WORD g_odczytanych_bajtow;

	unsigned char read_code;

	unsigned short x_pos, y_pos, k;

	//unsigned short sWidth, sHeight;
	unsigned char byte5;

	unsigned char byte9;
	unsigned char currentDataSectionLength;

public:
	int drawGIFImage(File, GIF_IMAGE*);
	unsigned char counter;
	void GIFDrawPixel(unsigned char);
	unsigned short imgWidth, imgHeight;
	led *framebuffer;
};

#endif

gif.cpp:

#include "gif.h"

int GIF::drawGIFImage(File fileObject, GIF_IMAGE * gifImage){
	unsigned int i;
	unsigned char bpp;
	unsigned short minLZWCodeLength;
	unsigned int GCT_size;

	g_fileObject = fileObject;
	
	g_fileObject.read((void*)&BUFFER, 6);
	if(checkSignature((char*)BUFFER) != 1) return -1;

	g_fileObject.read(&BUFFER, 7);


	(*gifImage).screenDesc.SCREEN_WIDTH = (BUFFER[1] << 8) + BUFFER[0];
	(*gifImage).screenDesc.SCREEN_HEIGHT= (BUFFER[3] << 8) + BUFFER[2];
	(*gifImage).screenDesc.BYTE_5				=  BUFFER[4];
	(*gifImage).screenDesc.BACKGROUND		=  BUFFER[5];
	(*gifImage).screenDesc.ASPECT_RATIO	=  BUFFER[6];	

	byte5		= (*gifImage).screenDesc.BYTE_5;		

	bpp = (byte5 & 0x07);															

	GCT_size = 2 << bpp;															


	if(byte5 & 0x80){

		for(i = 0; i < GCT_size; i++){
			g_fileObject.read(&BUFFER, 3);
			g_GlobalColorTable[i].R = BUFFER[0];
			g_GlobalColorTable[i].G = BUFFER[1];
			g_GlobalColorTable[i].B = BUFFER[2];
		}
	}
	else return -1;


	do g_fileObject.read(&BUFFER, 1);
	while(BUFFER[0] != 0x2C);

	g_fileObject.read(&BUFFER, 11);


	(*gifImage).imageDesc.LEFT 				= (BUFFER[1] << 8) + BUFFER[0];
	(*gifImage).imageDesc.UP					= (BUFFER[3] << 8) + BUFFER[2];
	(*gifImage).imageDesc.IMAGE_WIDTH = (BUFFER[5] << 8) + BUFFER[4];
	(*gifImage).imageDesc.IMAGE_HEIGHT= (BUFFER[7] << 8) + BUFFER[6];
	(*gifImage).imageDesc.BYTE_9			=  BUFFER[8];

	imgWidth	= (*gifImage).imageDesc.IMAGE_WIDTH;	
	imgHeight	= (*gifImage).imageDesc.IMAGE_HEIGHT;	
	byte9			= (*gifImage).imageDesc.BYTE_9;		

	minLZWCodeLength	= BUFFER[9] + 1;				
	currentDataSectionLength	= BUFFER[10];			

	code_CLEAR			= GCT_size;
	code_END				= GCT_size + 1;
	PrimaryDictSize = GCT_size + 2;
	PrimaryCodeSize = minLZWCodeLength;
	currentCodeSize = minLZWCodeLength;


	currentDictSize	= PrimaryDictSize;

	counter = 0;

	for(i = 0; i < MAX_DICT_SIZE; i++)
		Dictionary[i].prevCode = Dictionary[i].nextCode = 0;

	x_pos = 1; y_pos = 130;

	bitsRemaining = 0;

	while( (code = getNextCode()) != code_END){


		if(code == code_CLEAR){
			currentCodeSize	= PrimaryCodeSize;	
			currentDictSize	= PrimaryDictSize;	
			oldcode = getNextCode();			

			if(oldcode > currentDictSize){
				return -3;
			}
			GIFDrawPixel(oldcode);							
			continue;										
		}


		if(code < currentDictSize){
			code1 = code;
			code2 = 0;

			while(code1 >= PrimaryDictSize){
				Dictionary[code1 - PrimaryDictSize].nextCode = code2;
				code2 = code1;
				code1 = Dictionary[code1 - PrimaryDictSize].prevCode;
				if(code1 >= code2)
					return -3;
			}

			GIFDrawPixel(code1);								
			while(code2!=0){
				GIFDrawPixel(Dictionary[code2 - PrimaryDictSize].code);
				code2 = Dictionary[code2 - PrimaryDictSize].nextCode;
			}
			Dictionary[currentDictSize - PrimaryDictSize].code = code1;
			Dictionary[currentDictSize - PrimaryDictSize].prevCode = oldcode;
			++currentDictSize;								

			if(currentDictSize == MAX_DICT_SIZE) return -2;

			if((currentDictSize) == (0x0001<<currentCodeSize))
				++currentCodeSize;								
			if(currentCodeSize > 12)
				currentCodeSize = 12;

			oldcode = code;										
		else
		{
			code1 = oldcode;
			code2 = 0;

			while(code1 >= PrimaryDictSize){
				Dictionary[code1 - PrimaryDictSize].nextCode = code2;
				code2 = code1;
				code1 = Dictionary[code1 - PrimaryDictSize].prevCode;
				if(code1 >= code2)
					return -3;
			}

			GIFDrawPixel(code1);
			while(code2!=0){
				GIFDrawPixel(Dictionary[code2 - PrimaryDictSize].code);
				code2 = Dictionary[code2 - PrimaryDictSize].nextCode;
			}
			GIFDrawPixel(code1);

			Dictionary[currentDictSize - PrimaryDictSize].code = code1;
			Dictionary[currentDictSize - PrimaryDictSize].prevCode = oldcode;
			++currentDictSize;	
			
			if(currentDictSize == MAX_DICT_SIZE) return -2;


			if((currentDictSize) == (0x0001<<currentCodeSize))
				++currentCodeSize;

			if(currentCodeSize > 12)
				currentCodeSize = 12;

			oldcode = code;	
		}
	}
	return 0;
}


int GIF::checkSignature(char * IN){
	char signature[6];
	int i;

	for(i = 0; i < 6; i++)
		signature[i] = IN[i];

	if( (strcmp(signature,"GIF87a") == 0) || (strcmp(signature,"GIF89a") == 0) )
		return 1;
	else
		return 0;
}




unsigned short GIF::getNextCode(void){
	unsigned int retval=0, temp;

	if(bitsRemaining >= currentCodeSize){
		retval = (read_code & ((0x01 << currentCodeSize) - 1));
		read_code >>= currentCodeSize;
		bitsRemaining -= currentCodeSize;
	}
	else{
		retval = (read_code & ((0x01 << bitsRemaining) - 1));
		g_fileObject.read(&read_code, 1);
		++counter;
		if(counter == currentDataSectionLength){
			counter = 0;
			g_fileObject.read(&currentDataSectionLength, 1);
		}

		if((currentCodeSize - bitsRemaining) <= 8){
			temp = (read_code & ((0x01 << (currentCodeSize - bitsRemaining)) - 1));
			retval += (temp << bitsRemaining);
			read_code >>= (currentCodeSize - bitsRemaining);
			bitsRemaining = 8 - (currentCodeSize - bitsRemaining);
		}
		else{
			retval += (read_code << bitsRemaining);
			g_fileObject.read(&read_code, 1);
			++counter;
			if(counter == currentDataSectionLength){
				counter = 0;
				g_fileObject.read(&currentDataSectionLength, 1);
			}
			retval += ((read_code & ((0x01 << (currentCodeSize - bitsRemaining - 8)) - 1)) << (bitsRemaining + 8));
			read_code >>= (currentCodeSize - bitsRemaining - 8);
			bitsRemaining = 8 - (currentCodeSize - bitsRemaining - 8);
		}
	}
	return retval;
}



void GIF::GIFDrawPixel(unsigned char code){
	COLOR rgbColor;

	rgbColor = g_GlobalColorTable[code];			
	int pix = x_pos*imgHeight+y_pos;
	Serial.println((int)pix);
	Serial.println((int)rgbColor.R);
	Serial.println((int)rgbColor.G);
	Serial.println((int)rgbColor.B);
}

#5

The dictionary alone consumes all the RAM that you have available.


#6

As i can see here http://www.arturocampos.com/ac_lzw_gif.html#Gif decoding it must be.
Is there any methods to parse GIF file by pixels? Maybe somebody know method, which will work on STM32?


#7

bko, the max size is set to 100, making that array “theoretically” 32100 = 600 bytes in size. The only problem is I don’t know if the compiler allocates 2 or 4 bytes for a type “short”?

Nonetheless, 600 bytes is a “chunk-o-change” when you have RAM shortage.


#8

This eats up another 256 x 3
COLOR g_GlobalColorTable[256];

I’m not sure how much the SD library is eatin up, and I’m guessing you have more code that deals with the NeoPixels that allocates the bulk of the memory. How many pixels are you running?


#9

I’m commented for tests code that running NeoPixels, and working just with USB serial, WebSockets, SD library and this code.


#10

Oh, so memory is overflowing with just the above code? Oh I guess that’s only the GIF class… can you share any other memory you are allocating?


#11

Right you are on the dictionary size! I was looking at the comment which lists the largest possible dictionary size for the LZW compression engine. This code will not work on some GIFs with only 100 entries but it should work on most. About a lifetime ago, I wrote a GIF creator for taking screenshots on an early handheld device.

Yes, you need the LZW dictionary–there is no real way around that. The current linked list approach may not be the most space efficient data structure to use. If you are up for the challenge, you could change this to use a more efficient data structure. Particularly if the dictionary size is limited to 100 entries, you could change nextCode and prevCode to be 8-bit instead of 16-bit.

Another thing to consider: can you change the image at the source? Can you have the source do some decoding for you or make your life easier by limiting colors or something?

If you know you are displaying the GIFs on a black and white or 8-bit display, you might be able to make the GIF decoder more memory efficient.


#12

It’s NeoPixels so 8-bit color (256 colors) is probably a good limitation to start with.


#13

OK, so figure out what the color map in the GIF is (the part read by

if(byte5 & 0x80){

		for(i = 0; i &lt; GCT_size; i++){
			g_fileObject.read(&BUFFER, 3);
			g_GlobalColorTable[i].R = BUFFER[0];
			g_GlobalColorTable[i].G = BUFFER[1];
			g_GlobalColorTable[i].B = BUFFER[2];
		}
	}

and put that in flash in a const array in your program. Then just read past these bytes doing nothing.

I looked at my GIF creating code from long ago and I used a 5003 location hash table (5003 is 4096 + 7 for overflow) which is not going to work on the Spark core right now.

Does the file format have to be GIF? You can turn off compression completely for BMP files if you have control over the file.


#14

I’ve thinked many time about file format. And preffered GIF as maximum compatible format, which can be viewed on standard computer, smartphone etc, and in future i’ve planned to make uploading this to Core by WLAN, or by attaching it’s SD card to uploading device…
So for now - i will have server in my infrastructure, and that server can send commands to Spark Core. As i see - better way is to get gif files by server, decode it in RGB array and send command to core to download it from server and put on SD…
It’s sad, because i’m loosing possibility to upload GIF files into SD directly, but i don’t see any way to do correct parsing…