Arduboy image converter


(Andy) #1

I have made a converter online for translating images into a 1-bit hex array suitable for copying and pasting into the arduboy and rendering using the built-in ‘drawBitmap’ function. It automatically scales larger images to fit the display and fit into a number of bytes. Check it out: http://www.andrewlowndes.co.uk/blog/graphics/arduboy-image-converter


All The Arduboy Image Converters
What does PROGMEM mean?
Display Buffer Organization
Image2Arduboy - Image to Hex Byte Array Tool
#2

This is awesome!

I was working on some graphics earlier…I flipped the image vertically and saved it in Microsoft Paint as a monochrome bitmap (flipped because bitmap format stores images upside-down), then opened it in a hex editor, copied the hex bytes as text, pasted into Notepad, and replaced all spaces with “,0x”.

Needless to say this is a lot more convenient. :slight_smile:

Could you add an option to break up the output into 8x8 or 16x16 pixel chunks? That way we could feed it an image that already contains background tiles or sprites.


#3

Funny thing I have my as work in progress :slight_smile:


(karloz) #4

Good job

so many things come to mind


(Josh Goebel) #5

If you like easy and the command line. Put all your PNG resources into an assets folder. Run img2ard.rb and it will compile all your files into a single C header file called “assets.h” ready to compile.

For example you have these resources:

assets/ship.png
assets/enemy.png
assets/bullet.png

They will get compiled to:

#ifndef ASSETS_H
#define ASSETS_H

PROGMEM const unsigned char ship = {...}
PROGMEM const unsigned char enemy = {...}
PROGMEM const unsigned char bullet = {...}

#endif

The images are compiled suitably for using the the Arduboy drawImage function so they will be rendered to the screen as fast as they can.

No, it doesn’t do BMPs because those are so 1999… there is no reason it couldn’t in the future, but really the only reason everyone started using BMP is because I guess that was an easier format for early conversion tools to use. It’s certainly not a format in widespread use today. It took effort to even figure out how to get my modern graphics program to save a BMP the other day. The internal API would have to be abstracted to allow reading multiple images types. I’d be open to that if there was a huge demand but PNG are nice since you have colors (for future greyscale), transparency, etc.

If someone wants to make PR for windows specific instructions (if there are any) I could include that in the README. Latest OS X ships with Ruby 2, so this should just work out of the box (after you install the necessary gem).


(Albert Filice) #6

What’s the drawImage function? I can’t seem to find it anywhere.


(Josh Goebel) #7

I mean drawBitmap…


#8

This is great! I was just looking to see if someone already created a utility. Thanks!


(Mike) #9

Hey guys - quick question.

So I’m attempting to convert an image that’s 128x64 px in size, and I’m getting an array out of this tool that’s 1024 bytes long. My kit hasn’t arrived yet (Got a ‘hey come pick this up’ notice from the post office today though!), and I’m writing my game in SDL for the time being. I don’t think it’s a tool problem, I suspect that I’m misunderstanding something.

I essentially wrote a function that runs though a byte (char) array and looks for 0xFF. If found, it draws a pixel. If not, it doesn’t. Below is my image, and right below that is my code for drawing this in SDL.

This is huge, sorry. I figured the entire array was relevant though.

{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0x87, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0x7F, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0x47, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0x07, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0x06, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0x06, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 
0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 
0x40, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0xC1, 
0x40, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, 0x83, 
0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0x00, 
0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0x10, 
0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x02, 0x71, 
0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x84, 0x71, 
0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x20, 
0x20, 0x00, 0x00, 0x7F, 0xFF, 0x07, 0x39, 0x99, 
0x83, 0x04, 0x0C, 0x1F, 0xFF, 0xFF, 0x80, 0x04, 
0x10, 0x16, 0x00, 0x7F, 0xFF, 0x07, 0x1D, 0x99, 
0x83, 0x04, 0x0C, 0x0F, 0xFF, 0xFF, 0x80, 0x06, 
0x60, 0x02, 0x00, 0x7F, 0xFF, 0x77, 0x1D, 0x99, 
0xBB, 0x77, 0xBD, 0xCF, 0xFF, 0xFF, 0x00, 0x06, 
0xE8, 0x78, 0x00, 0x7F, 0xFF, 0x77, 0x1D, 0x9B, 
0xBB, 0x77, 0xBD, 0xFF, 0xFF, 0xFF, 0xC1, 0x26, 
0xE0, 0x28, 0x00, 0x3F, 0xFF, 0x77, 0x5C, 0x9B, 
0x83, 0x77, 0xBE, 0x7F, 0xFF, 0xFF, 0xD3, 0x36, 
0x1F, 0x00, 0x80, 0x1F, 0xFF, 0x06, 0x5C, 0xAB, 
0x83, 0x77, 0xBF, 0x3F, 0xFF, 0xFF, 0xE3, 0x3E, 
0xFF, 0x80, 0x00, 0x1F, 0xFF, 0x4E, 0xCC, 0xA3, 
0xBB, 0x77, 0xBF, 0x9F, 0xFF, 0xFF, 0xF3, 0x3E, 
0xFF, 0x80, 0x00, 0x1F, 0xFF, 0x6E, 0x0E, 0x63, 
0xBB, 0x77, 0xBF, 0xCF, 0xFF, 0xFF, 0xBB, 0xFF, 
0xFF, 0x80, 0x00, 0x8F, 0xFF, 0x66, 0x0E, 0x67, 
0xBB, 0x77, 0xBD, 0xCF, 0xFF, 0xFF, 0x7D, 0xFF, 
0xFF, 0x80, 0x00, 0x87, 0xFF, 0x64, 0xEE, 0x67, 
0x83, 0x07, 0xBC, 0x0F, 0xFF, 0xFF, 0x78, 0x7F, 
0xFE, 0x40, 0x00, 0xE7, 0xFF, 0x70, 0xEE, 0x67, 
0x83, 0x07, 0xBC, 0x1F, 0xFF, 0xFE, 0xA8, 0x00, 
0xFC, 0x40, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xA8, 0x00, 
0x70, 0xE0, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x03, 
0x00, 0x61, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 
0x80, 0x01, 0x98, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x30, 0xDC, 
0xE0, 0x01, 0x9C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x1C, 
0xF0, 0x01, 0xDC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 
0xF0, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 
0xC0, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 
0x20, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 
0x80, 0x20, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFB, 0xFF, 0xFE, 0xFF, 0xFF, 0xF8, 0x00, 
0x00, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFB, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0x87, 
0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFB, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 
0x00, 0x07, 0xFF, 0xFF, 0xFE, 0x98, 0x4D, 0xBF, 
0xFF, 0xFB, 0x0C, 0x30, 0xFF, 0xFF, 0xFF, 0xFF, 
0x00, 0x0F, 0xFF, 0xFF, 0xFE, 0x5B, 0x6D, 0xBF, 
0xFF, 0xFB, 0x6D, 0xB6, 0xFF, 0xFF, 0xFF, 0xFF, 
0x00, 0x1F, 0xFF, 0xFF, 0xFE, 0xDB, 0x69, 0xBF, 
0xFF, 0xFB, 0x6F, 0xB6, 0xFF, 0xFF, 0xFF, 0xFF, 
0x00, 0x3F, 0xFF, 0xFF, 0xFE, 0xD8, 0x69, 0x7F, 
0xFF, 0xFB, 0x6C, 0x36, 0xFF, 0xFF, 0xFF, 0xFF, 
0x00, 0x3F, 0xFF, 0xFF, 0xFE, 0xDB, 0xE2, 0x7F, 
0xFF, 0xFB, 0x6D, 0xB6, 0xFF, 0xFF, 0xFF, 0xFF, 
0xC0, 0x1F, 0xFF, 0xFF, 0xFE, 0xDB, 0x72, 0x7F, 
0xFF, 0xFB, 0x6D, 0xB6, 0xFF, 0xFF, 0xFF, 0xFF, 
0xC0, 0x1F, 0xFF, 0xFF, 0xFE, 0xD8, 0x76, 0x7F, 
0xFF, 0xFB, 0x0C, 0x30, 0xFF, 0xFF, 0xFF, 0xFF, 
0xE0, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xE0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xF7, 0xED, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xED, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFD, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFD, 0x3B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};


        int drawx=0, drawy=0;
        for(int pos=0; pos < 1024; pos++)
        {
            drawx++;
            if(drawx>128)
            {
                drawy++; drawx=0;
            }
            if(menuGraphic[pos] != 0xFF)
            {
                SDL_RenderDrawPoint(ren, drawx, drawy);
                cout << "Got a FF!" << endl;
            }
        }

So basically I get a squished image on screen that looks nothing like my image. I get the feeling that I’m missing something huge with the way that I’m drawing…

Any thoughts guys?


(Andy) #10

The output of the editor is designed to work with the ‘drawBitmap’ function, which uses blocks of bytes (each ‘0x…’) to store a 0 or 1 for each 8 pixels. It looks like you are checking to see if all pixels are active in the block and then drawing a point rather than going through all of the individual pixels. You may be better of porting the ‘drawBitmap’ function to work with SDL rather than reimplementing it if you intend to just drop in an image and start using it. Below is the main code from drawBitmap in the image converter. You can see a bitwise shift through each block so every pixel can be read.

for (var i=0, i2=0; i<bitmap.length; i++, i2+=8) {
    var line = pgm_read_byte(bitmap, i);
    var x2 = x + (i2 % w), y2 = y + Math.floor(i2 / w);
    
    for (var j = 7; j>=0; j--) {
        if (line & 0x1) {
            this.drawPixel(x2+j, y2, color);
        }
        
        line >>= 1;
    }
}

(Mike) #11

Hey Andy,

Awesome, thanks! I’m going to have to dissect that code, I can’t wrap my brain around drawing 8192 pixels with just 1024 bytes of source data… Time to review bit shifting!

Edit: In case anyone else comes along with the same question that I had, be sure to check out this tutorial on bitwise operators: http://www.cprogramming.com/tutorial/bitwise_operators.html


#12

1024 bytes with eight pixels per byte = 8192 pixels. :wink:


(Mike) #13

Haha yep! I managed to work it out… using a byte like an array… genius!


(Josh Goebel) #14

Yeah a byte is just an array of 8 bits. :slight_smile: To do any fast rendering though the bitmaps need to bit-shifted at compile time so they can hit the hardware as fast as possible… though for doing vertical offsets other than 8 pixel intervals some shifting still has to be done in software. The fastest rendering I know of is the all assembly rendering code I’ve written to draw sprites with transparency information… the data and transparency info all being mixed into the same data array in a format that is most efficient for the CPU, not for human understanding (i.e., it’s interleaved).

This CPU can do amazing things if you understand the raw instruction cycles watch out for huge loops that do memory access. The limitation is you really only have 3 16-bit registers (two 8-bit register pairs that can inc/dec in a single instruction) so if you want to draw from PROGMEM to display that requires 2 16-bit registers/pointers. If you want to draw at an offset not divisible by 8 then you need two pointers into RAM (for the upper and lower 8 pixel “rows”)… and to get access to all 3 of those 16-bit pairs requires dropping to assembler - C won’t use them all otherwise.

Anyways once you get the assembly actually working it’s crazy fast. :smile:


(Alberto) #15

This is what I’m using right now http://www.csmithsoftware.com/LCD_Creator/LCD_Creator.dmg


(Gavin Atkin) #16

Where can you’re tool be downloaded, Dreamer3?


#17

@Gaveno it is what I’m using :wink: and the tool requires Ruby 2.0 (windows: http://rubyinstaller.org/ )

tool: https://github.com/yyyc514/img2ard


(Gavin Atkin) #18

With a little explanation on the algorithm used to store the bits, I could I could create a Windows tool pretty easily. Or maybe even a html5 version. It’s a much better format so it would be ideal to get it in the hands of as many community members as possible for better games :).


#19

This is a (very) small image 8x8 pixels. The pink is normally transparent and the filetype is png.
This is the output:

PROGMEM const unsigned char smile[] = {
// width, height
8, 8,
0x00, 0x3C, 0x6A, 0x5E, 0x5E, 0x6A, 0x3C, 0x00
};

PROGMEM const unsigned char smile_mask[] = {
// width, height
8, 8,
0x3C, 0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C
};

PROGMEM const unsigned char smile_plus_mask[] = {
// width, height
8, 8,
0x00, 0x3C, 0x3C, 0x7E, 0x6A, 0xFF, 0x5E, 0xFF, 0x5E, 0xFF, 0x6A, 0xFF, 0x3C, 0x7E, 0x00, 0x3C, 
};

SO the function always includes width and height (very handy)
it spits out

  • the sprite without mask (so the transparent is black)
  • the mask itself
  • the sprite combined with the mask*
  • this is a mix of both: 1 byte of each, alternating like this:

first byte sprite, first byte mask,
second byte sprite, second byte mask,
… byte sprite, … byte sprite,
last byte sprite, last byte mask,


(Gavin Atkin) #20

Bits are stored the same way as with the regular draw bitmap method, correct? As in the same as the display buffer?