Convert PImage to byte array

Hi !

I’m looking for a way to convert a Pimage to a byte array. I feel like this should be a pretty common things to do, and I tried plenty of things found online but, not a single one seems to work.
If anyone knows how to easily convert a Pimage to a byte array that would really help me. For the context, I need to convert it before sending thanks to the mqtt library.

Thanks you

2 Likes

it is a gray scale image?

RGB has int as datay type, not byte?

Chrisir

it’s a RGb image, i know it’s an int data type but i wanna convert it to a byte array

obviously you will have a big loss of data, e.g. only gray or only brightness

but when you look at loadPixels() and pixels[] you can just program the function yourself

https://www.processing.org/reference/pixels.html

Chrisir


color c1 = color (#AD2A2A); 

fill(c1); 

background(c1); 

println (c1); 

byte b1 = byte (c1); 

println (b1);

Ok so the only way is to convert it pixel by pixel trough a loop using byte() and adding it to a byte array ?

I don’t know whether there are other ways

Hello,

If you convert a 32 bit int (4 bytes) using byte() you lose all but the first byte which only represents B (blue).

Read on…

color is stored as a 32 bit int (4 bytes):

You will have to extract 4 bytes and store these into a byte array; 1 pixel will have 4 elements in a 1 dimensional array. You will have to decide on the correct order for A, R, G, B for your context.

I did bit manipulation:

An example (left some for you to think about) using bit manipulation to extract the R and G:

  byteArray[0] = ?                                              >> 0);  //B
  byteArray[1] = byte((pixels[i] & 0x0000FF00) >> 8);  //G
  byteArray[2] = ?                                              >> 16); //R
  byteArray[3] = byte((pixels[i] & 0xFF000000) >> 24); //A

You can also use these functions to extract color.
Reference / Processing.org There are others!

The examples do comment on bit manipulation being faster!
And they did it by shifting first and then masking.

It is simple enough to write a function to do this; I started with above and iterated it down to a nice clean function to do this.

I do not know what the library is expecting but you can easily adapt code for this.

:)

Other references:

2 Likes

Thanks ,I’ll try that

Ok so I found an easier way to do that.
I just save the image as a local file and then I open it this way :

byte b[] = loadBytes("image.jpg");

And I have my byte array, ready to send.

3 Likes

If the bytes in the image are in the right order for you, you should be able to use something like (coding off top of my head! :smile:) -

byte[] data = new byte[width * height * 4];
int[] pixels = ...
ByteBuffer.wrap(data).asIntBuffer().put(pixels);

Do you want encoded or raw data?

Also, it should be byte[] b = if you don’t want to confuse everyone.

3 Likes

Saving the image to a local file is a nice idea and will work provided you don’t want to recreate the image. This is not possible because although you can load the original pixel data you don’t know the width and height of the original image.

In the code below the image width and height are stored alongside the pixel data in the byte array allowing the image to be recreated like this
warn2x

The sketch contains two handy functions to do the conversions byte[] > PImage and PImage > byte[]

import java.nio.ByteBuffer;

void setup() {
  size(400, 300);
  background(255);
  fill(0);
  textSize(16);
  PImage imgOrg = loadImage("warn.png");
  text("Original", 20, 16);
  image(imgOrg, 20, 40);
  // Not create byte array
  byte[] array = convertImageToByteArray(imgOrg);
  // Now recreate a copy of the original from the byte array
  PImage imgNew = convertByteArrayToImage(array);
  text("Byte array > image", 220, 16);
  image(imgNew, 220, 40);
  save("warn2x.png");
}

byte[] convertImageToByteArray(PImage img) {
  img.loadPixels();
  int[] px = img.pixels;
  ByteBuffer bb = ByteBuffer.allocate((2 + px.length) * 4);
  bb.putInt(img.width);
  bb.putInt(img.height);
  for (int d : px) {
    bb.putInt(d);
  }
  return bb.array();
}

PImage convertByteArrayToImage(byte[] bytes) {
  ByteBuffer bb = ByteBuffer.wrap(bytes);
  int w = bb.getInt();
  int h = bb.getInt();
  PImage img = new PImage(w, h);
  int[] px = img.pixels;
  for (int i = 0; i < px.length; i++) {
    px[i] = bb.getInt();
  }
  img.updatePixels();
  return img;
}

here is the image if you want to try it out
warn1

4 Likes

Hello,

Keep in mind that a jpg is a lossy compression algorithm and the receiving end will not reproduce the original. It all depends on what your requirements are.
This is one article on the subject (there are many):

:)

2 Likes

JPG-format does not contain pixels in arrays, so you will get garbage. If you really want to go with image-file way you should use ppm https://en.wikipedia.org/wiki/Netpbm#File_formats. It has a few leading bits: P3, width, height and colour depth, before pixel array starts.

I have used it to create an image-file and converted that file to png for later usage.

Otherwise it is loadPixels() and going through all pixels to create byte array. It’s actually pretty fast and quite easy once you get hang of it.

1 Like

I’ve been struggling with this challenge of converting a PImage to a byte array. I cannot get it to work. I tried all of the above etc.

My intention is to send my byte array, which is a conversion of an image I created in my Processing application, to an external MCU connected to the serial port of my computer so that this image can be displayed on a e-Paper or e-Ink device.

The e-Paper display only shows black and white images, so I am not grappling with RGB or transparency.

To test my image, I have saved the image as a JPEG and then I used this utility https://lvgl.io/tools/imageconverter to convert the JPEG as a byte array using the option “Indexed 2 colors”.

This utility works well and the generated byte array is used by the MCU to display the image.

This webpage uses a PHP script and this code is opensourced on GitHub: lv_utils/img_conv_core.php at master · lvgl/lv_utils · GitHub

The PHP code looks more complicated than it really is, as it covers all options from raw, true color and multiple alpha and indexed options. I am simply using the indexed 2 color option, which is referenced as “const CF_INDEXED_1_BIT = 8;” in the PHP script. The script also includes dithering conversion options which are not needed either.

I now want to avoid saving my Processing create image as a JPEG and simply save the pixels as a byte array.

However, I cannot get my bytes to match up with the bytes generated by the webpage.

For starters, I have an image which is 128 pixels wide and 296 pixels high. The byte array generated by the PHP script is 16 x 296. So 8 pixels is converted into one byte.

Hence, I am stuck as using get(x,y) or referencing pixels[ i ] does not work.

So I had hoped that pixelDensity work here… as according to documentation " When the pixel density is set to more than 1, it changes all of the pixel operations including the way get() , set() , blend() , copy() , and updatePixels() all work."

But this is display or monitor dependent and I got an error message. Also you cannot use anything higher than 2.

Maybe there is a simple way to use the PHP script… it uses a class structure, which is a good start, but actually there is only one function and one private function that matters, so it is relatively straightforward for the experts out there.

I got stuck on the bit manipulation section as could not figure out the java/wiring equivalent:

    $w = $this->w >> 3;
            if($this->w & 0x07) $w++;

            $p = $w * $y + ($x >> 3) + 8;                       /* +8 for the palette*/
            if(!isset($this->d_out[$p])) $this->d_out[$p] = 0;          /*Clear the bits first*/
            $this->d_out[$p] |= ($c & 0x1) << (7 - ($x & 0x7));

Any suggestions are all greatly appreciated thanks.

I have converted what I thought was relevant and it is almost? working. It is certainly an improvement to what I tried manually by trying to convert 8 pixels into 1 byte etc.

IMG_20210904_210929

The code I created was as follows:

void setup() {
  // Not using size as there is nothing to display (for now) and printing output to console
  PImage img = loadImage("image.jpg");
  int d_out[] = new int[4736];    // this is 16 x 296 (I'm ignoring the first 8 bytes as per PHP code)
  
  /*Convert all the pixels*/
  int $w = img.width >> 3;
  for(int $y = 0; $y < img.height; $y++) {
      for(int $x = 0; $x < img.width; ++$x){
          int $p = $w * $y + ($x >> 3);                       /* +8 for the palette*/
          d_out[$p] |= (img.get($x,$y) & 0x1) << (7 - ($x & 0x7));
      }
  }
  
  int $i = 0;
  for(int $y = 0; $y < img.height; $y++) {
      String $c_array = "";
      for(int $x = 0; $x < img.width; $x++) {
          if(($x & 0x7) == 0) {
              $c_array += "0x" + hex(d_out[$i], 2) + ", ";
              $i++;
          }
      }
      println($c_array);
  }
  
}

void draw() {}

So I am not sure where I’ve gone wrong.

One aspect I was not sure of was how the indexing process worked within the PHP script and whether this was necessary to do within my Processing code.

Here is the PHP code extract:

$img_tmp = imagecreatetruecolor($this->w, $this->h);

        imagecopy ($img_tmp, $this->img, 0 , 0 , 0 , 0, $this->w , $this->h);

        imagetruecolortopalette($this->img, false, $palette_size);

        $real_palette_size = imagecolorstotal($this->img);                          /*The real number of colors in the image's palette*/

        for($i = 0; $i < $palette_size; $i++) {

            if($i < $real_palette_size) {

                $c = imagecolorsforindex ($this->img , $i);

                array_push($this->d_out, $c['blue'], $c['green'], $c['red'], 0xFF);

            } else {

                array_push($this->d_out, 0xFF, 0xFF, 0xFF, 0xFF);

            }

        }

This “pushed” 8 extra bytes into the array, which I do not need for my purposes so I left this out.

Any suggestions greatly appreciated.

Thanks

1 Like

Hello,

I just threw this together to work my brain:

Code
// Packing and Unpacking 8 bits
// v1.0.0
// GLV 2021-09-04

// https://discourse.processing.org/t/convert-pimage-to-byte-array/20268

PImage img;
PGraphics pg;

byte bin [] = new byte [32/8*32]; //32/8*32 = 128 byte packed array

void setup() 
  {
  size(100, 100);

  noSmooth(); //Otherwise it "smoothed" the output
  
  pg = createGraphics(32, 32); //32*32 = 1024 byte unpacked array
  
  pg.beginDraw();

  pg.background(255);
  pg.noFill();
  pg.stroke(0);
  int rx = 8;
  pg.rect(rx, rx, pg.width-2*rx, pg.height-2*rx);
  pg.circle(pg.width/2, pg.height/2, 28);
  pg.line(0, 1, pg.width/2, 1);
  pg.line(0, 3, pg.width/3, 3);
  pg.endDraw();
  
  pg.save("test.png");
 // pg.loadPixels();
  
  image(pg, 0, 0);
  
  println("Test:");
  test();
  println();
  
  println("Pack:");
  pack01();
  println();
  
  println("Unpack:");
  unpack00();
  println();  
  
  println(bin.length);
  println(pg.pixels.length);
  println(pg.width/8f);
  }

//void draw() 
//  {
//  background(0);
//  }


void test()
  {
  int [] pixels = {0, 3, 0, 3, 0, 3, 3, 3,
                   0, 3, 0, 3, 0, 3, 0, 3};
  int b = 0;
  for (int i = 0; i<pixels.length; i++)
    {
    b |= (pixels[i] & 1) << (7-i%8);
    if ((i+1)%8 == 0)
      {
      println(binary(b, 8));
      b=0;
      }
    }
  }  

void pack01()
  {
  int bc = 0;
  for(int y = 0; y<pg.height; y++)
    {
    int b = 0;  
    for(int x = 0; x<pg.width; x++)
      {
      int pl = x+y*pg.width;  //for array only!
      
      int bit = pg.pixels[pl] & 1;
      //print(bit);
      char tbit = (bit == 0 ? 'O' : '-');
      print(tbit); 
      
      b |= (pg.pixels[pl] & 1) << (7-x%8);
      if ((x+1)%8 == 0)
        {
        //print(binary(b, 8)); 
        print(' ');
        
        bin[bc] = byte(b);
        bc++; 
        
        b = 0;
        }
      } 
    println();   
    }
  }   


void unpack00()
  {
  for(int y = 0; y<pg.height; y++)
    {
    for(int x = 0; x<pg.width/8; x++)
      {
      int pl = x+y*pg.width/8;
      
      for (int i = 7; i>=0; i--)
      //for (int i = 0; i<8; i++)
        {
        int px = (bin[pl] >>> i) & 1;
        //print(px); //ok
        char tbit = (px == 0 ? 'O' : '-');
        print(tbit); 
        
        if (px == 0)
          stroke(0);
        else
          stroke(255);
        
        point( (7-i) + x*8 + width/2, y);
        } 
      print(" ");  
      }
      println();  
    }     
  }

I am “packing” 8 bytes (0 or 1 for color) into 8 pixels (one byte) and then “unpacking” to display.

I used a very small image so I could also print to console for debugging… it helped.

There may be something of interest in there for you.

:)

1 Like

Thanks so much. Your packing code worked a charm.

Instead of print(binary(b, 8)); I used print("0x" + hex(b,2)); instead.

and used print(", ") as my spacer.

1 Like

It was a good workout! I learned a lot along the way…

I still have to convert the unpacked back to a picture.

Another day…

All the best with your project.

:)

1 Like