Run-Length encoding image compression in Java

6.1k views Asked by At

Okay so i've got a University assignment where I need to compress an image using both Run-length encoding and huffman encoding. I'm focusing on the Run-Length encoding atm since I don't think i'm going to have time to implement the huffman.

What I am currently doing is passing in a buffered image, Then doing

public byte[] byteArray(BufferedImage image){
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    byte[] imageInByte = null;
    try{
        ImageIO.write(image, "BMP", baos);
        baos.flush();
        imageInByte = baos.toByteArray();
        baos.close();
    }catch(IOException e){
        System.out.println(e.getMessage());
    }

    return imageInByte;
}

to get the byte's of the image. I then take that and do the actual compression, to do this atm I'm using a stringBuffer which I'm pretty sure is wrong but I can't think of another way to do it. so the code for that is

public String getRunLength(){
    StringBuffer dest = new StringBuffer();        
    for(int i =0; i < imageByteArray.length; i++){
        int runlength = 1;
        while(i+1 < imageByteArray.length && imageByteArray[i] == imageByteArray[i+1]){
            runlength++;
            i++;

        }     


        dest.append(runlength);  

        dest.append(imageByteArray[i]);

    }
    return dest.toString();
}

I'm pretty sure I shouldn't be converting to a string since when I go back to the bytes then I will be getting the ascii values rather than the actual bytes. But I can't figure out how I would go about appending the run length to a standard Byte Array efficiently (I think I could do it if I appended the run length to the start, then moved everything after byte[i+runLength] down runLength amount in the array.. but that would be extremely inefficient and prone to error... probably)

I then need to save it as an image, which obviously isn't working at the moment but the code i've currently got for that is

 try{
        File newImage = new File("Saved.png");
        ImageIO.write(rleImage, "BMP", newImage);
    }catch(Exception e){
        System.out.println("something fucked up");
    }

Thanks for any help you might be able to provide :)

Just noticed I missed out the part where I set the rleImage that is done like

 public BufferedImage stringToImage(String runLengthEncode){
    ByteArrayInputStream bais = new ByteArrayInputStream(runLengthEncode.getBytes());
    try{
        imageRLE = ImageIO.read(new ByteArrayInputStream(runLengthEncode.getBytes()));
    }catch(IOException e){

    }
    //decode(runLengthEncode);
    if(imageRLE == null)
        System.out.println("imageRLE is null");
    return imageRLE;
}
2

There are 2 answers

3
AgilePro On BEST ANSWER

You should be able to use a ByteArrayOutputStream in the exact same way you use the StringBuffer:

public byte[] getRunLength(){
    ByteArrayOutputStream dest = new ByteArrayOutputStream();        
    for(int i =0; i < imageByteArray.length; i++){
        int runlength = 1;
        while(i+1 < imageByteArray.length && imageByteArray[i] == imageByteArray[i+1]){
            runlength++;
            i++;

        }     

        dest.write((byte)runlength);  
        dest.write((byte)imageByteArray[i]);
    }
    return dest.toByteArray();
}

This avoids the whole convert to char and back.

By the way, the algorithm is inefficient and probably wrong. You iterate over each character, and then for each character you look forward for the span of characters. You don't need to do that. You are already walking through all the characters, so all you need to do is remember what the last character was, and ac accordingly.

public byte[] getRunLength(){
    ByteArrayOutputStream dest = new ByteArrayOutputStream();  
    byte lastByte = imageByteArray[0];
    int matchCount = 1;
    for(int i=1; i < imageByteArray.length; i++){
        byte thisByte = imageByteArray[i];
        if (lastByte == thisByte) {
            matchCount++;
        }
        else {
            dest.write((byte)matchCount);  
            dest.write((byte)lastByte);
            matchCount=1;
            lastByte = thisByte;
        }                
    }
    dest.write((byte)matchCount);  
    dest.write((byte)lastByte);
    return dest.toByteArray();
}

You will see that this touches each byte value only once.

0
Bassel alshayeb On

you are using flush in ByteArrayOutputStream this will give you wrong information to make your code run just remove the line

public byte[] byteArray(BufferedImage image){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] imageInByte = null;
try{
    ImageIO.write(image, "BMP", baos);

    imageInByte = baos.toByteArray();
    baos.close();
}catch(IOException e){
    System.out.println(e.getMessage());
}

return imageInByte;
}