How to save image with different settings? (bit depth, etc)

edited June 2018 in How To...

I have a small .bmp file that weighs 1296 bytes. When I do this string, it saves it as a .bmp files that weighs 558 bytes:

loadImage(dataPath("test.bmp")).save(dataPath("test2.bmp"));

I assume this is because it saves it with a different bit depth. What can I do to make it save with bit depth of the original image? I need it to have same everything as the original file for technical reasons. Here's my test file: https://www.dropbox.com/s/1b08oe0j3w8kc78/test.bmp

And here's what I get: https://www.dropbox.com/s/kqjulvbhai9t0qm/test2.bmp

edit: I'm not looking for just copying the image, which obviously can be done by copying the file over. I want to be able to edit them with functions like .get() and .set() and then save the result in the same format and with same characteristics as the input file.

edit2: Also I'm not looking for any size or efficiency advantages at all - an old videogame I'm working with just refuses to work with test2.bmp, while it can take test.bmp perfectly. And, from a bit of testing with different files I've concluded that the difference between input and output file that Processing introduces is what causes it.

Tagged:

Answers

  • Check javaDocs for File. I believe there is a copy() function for files. as an alternative, you can open the file as a binary stream and save it as such.

    Kf

  • Actually, sorry for not clarifying, I want to be able to open it up, and use functions like .get() and .set() to edit the picture and save the edited result in exactly the same format and with same characteristics as the input file.

    Maybe there's a way to use .get() function on a PImage object and then use saveBytes() to save it? Since it's .bmp I think it shouldn't be too hard...

  • identify *.bmp
    
    test2.bmp BMP3 9x18 9x18+0+0 8-bit sRGB 558B 0.000u 0:00.000
    test.bmp BMP3 9x18 9x18+0+0 8-bit sRGB 256c 1.3KB 0.000u 0:00.000
    

    (identify is part of imagemagick)

    test.bmp is 256 colours, test2.bmp isn't. but both are 8 bits deep. so the first image contains a 256-colour palette, most of which it doesn't use.

  • with a bigger image, using 256 colours will be more efficient, but these things are tiny so you don't get the benefits.

  • As I said, I need the result file to be the same as the input file for technical reasons. I don't care about size or efficiency here at all. Those technical reasons being that I'm trying to create a tool for mod developers of a game called "Cortex Command", which is pretty old, and it just refuses to work well with images that Processing outputs.

    So far this exact difference between input and output files seems to be the problem of this.

  • It is possible to write something that writes the data out as a 256 colour bitmap, same as the input, but you'd need to do everything yourself.

    http://www.dragonwins.com/domains/getteched/bmp/bmpfileformat.htm

  • Allright then. Not the way to solve the problem I wanted to use, but still, thanks for the link!

  • edited June 2018

    When you use a graphic program like irfanview , gimp or photoshop you might be able to load any processing generated image and save it with exact the properties you need (256 colors etc.)

  • I know I can re-save it using other software, but that would be something I want my Processing sketch to do on itself alone. I'm thinking about automating the process, and re-saving every result image by hand using some other program would take a lot of time.

    Also, currently working with the saveBytes idea. Never dug into binary and byte stuff deep enough, so that's a good practice.

  • edited June 2018

    This code from PImage in saveImageIO might be related to your issue?:

    https://github.com/processing/processing/blob/e83ae2054a3d3c52b1663385673fdfb26abcc96b/core/src/processing/core/PImage.java#L3221

      // JPEG and BMP images that have an alpha channel set get pretty unhappy.
      // BMP just doesn't write, and JPEG writes it as a CMYK image.
      // http://code.google.com/p/processing/issues/detail?id=415
      if (extension.equals("bmp") || extension.equals("jpg") || extension.equals("jpeg")) {
        outputFormat = BufferedImage.TYPE_INT_RGB;
      }
    

    Just a guess based on searching the source code for "bmp", but perhaps your source BMPs have an alpha channel, and which is getting stripped out by Processing -- hence the reduced file size and the compatibility problems.

  • No, it's the palette. Run xxd on the file and you can see it after the two headers. I'm not sure it's used but it's there.

  • Actually, you've got me wondering whether that's right now given that I did it in a rush at lunchtime...

  • there's definitely a palette in there (you can see it in gimp)

    00000000: 424d                                     BM               BM
                   1005 0000                             ....           SIZE
                             0000 0000                       ....       RESERVED
                                       3604 0000                 6...   START OF DATA
                                                 2800                (. HEADER SIZE (0x28 = 40)
    00000010: 0000                                     ..
                   0900 0000                             ..             PIXEL WIDTH = 9
                             1200 0000                     ....         PIXEL HEIGHT = 18
                                       0100                    ..       PLANES = 1
                                            0800                 ..     BITS PER PIXEL = 8
                                                 0000              .... COMPRESSION (NONE)
    00000020: 0000                                     ..
                   0000 0000                             ....           IMAGE SIZE
                             120b 0000                       ....       HORIZONTAL RES
                                       120b 0000                 ....   VERTICAL RES
                                                 0000                .. NUMBER OF COLOURS
    00000030: 0000
                   0000 0000                                            IMPORTANT COLOURS
                             ff00 ff00 3838 4800 4848  ..........88H.HH COLOUR TABLE
    00000040: 5800 6868 7900 e3e6 f800 ffef 4f00 0f18  X.hhy.......O...
    00000050: 4a00 2028 5800 2428 7b00 3838 5800 0808  J. (X.$({.88X...
    00000060: 3b00 091c 8000 0b1b a800 091a e700 4d58  ;.............MX
    00000070: 6b00 5867 7800 6c77 a100 8a96 ab00 8ca1  k.Xgx.lw........
    00000080: cb00 b0c0 fb00 3a2d 1e00 0e18 2900 1322  ......:-....).."
    00000090: 3900 2028 4800 2838 4800 2038 5800 2838  9. (H.(8H. 8X.(8
    000000a0: 6e00 3947 6800 2848 7800 3946 7800 2848  n.9Gh.(Hx.9Fx.(H
    000000b0: 7800 3047 8800 2c48 9c00 3058 8800 3b58  x.0G..,H..0X..;X
    000000c0: 9800 305f b200 3c6f a800 6f93 ce00 6383  ..0_..<o..o...c.
    000000d0: f900 889b f900 1018 3800 0828 5800 1028  ........8..(X..(
    ...
    
  • Yes, it's most definitely the palette. I figured that out too when working on custom code to make it work. Apparently the game uses its own palette for every image file it uses. I ended up just copying the first 1078 bytes(54 for header, 1024 for palette) from one of the sprites and pasting that at the beginning of every file my code outputs, with needed changes like file size and width/height of the image.

    I managed to accomplish my goal with saveBytes() already, just saying. I don't think that I should mark any replies as answers since that would confuse people with the same question. (forgive my possible dorkiness, not an often forum dweller .-.)

  • Coming back to my irfanView approach: irfanView has a batch Mode which means you can apply an operation to a bunch of images at once

  • Glad you worked out a solution!

    I ended up just copying the first 1078 bytes(54 for header, 1024 for palette) from one of the sprites and pasting that at the beginning of every file my code outputs, with needed changes like file size and width/height of the image.

    Is this something simple enough that you could share it as a few lines of example code, in case someone else does have a similar problem?

  • edited June 2018

    It's quite a long set of code, actually, but I'll still paste it in, because someone might need it later. It also requires an additional "palette" file, so I'll link that too:

    https://www.dropbox.com/s/r3u1gb8cm234qc4/palette?dl=0

    byte[] byte2(int a){        //Converts an int into 4 bytes packed into an array
        byte[] ret = new byte[4];
        ret[0]=(byte)((a)&0xFF);
        ret[1]=(byte)((a>>8)&0xFF);
        ret[2]=(byte)((a>>16)&0xFF);
        ret[3]=(byte)(a>>24&0xFF);
        return ret;
    }
    
    byte[] append(byte[] in,byte[] more){ //Allows appending an array of bytes to another array of bytes
     for(int i=0;i<more.length;i++){
       in=append(in,more[i]);
     }
      return in;
    }
    int bitpos(int x,int y,int width){ //Converts input X Y coordinates into a byte number
      return x+y*(bitoffset(width));
    }
    int bitx(int in,int width){ //Converts a byte number into an X coordinate
      return in%(bitoffset(width));
    //return 0;
    }
    int bity(int in,int width){ //Converts a byte number into an Y coordinate
      return floor(in/(bitoffset(width)));
    }
    boolean bitgood(int in,int width){ //Can I write in this byte, or it's one of those 4 spacing bytes at the end of each horisontal line?
      return in%(bitoffset(width))<width;
    }
    
    int bitoffset(int width){ //To account for these 4 additional spacing bytes at the end of each horisontal line.
    return (((width - 1) / 4) + 1) * 4;
    }
    
    int unsignbyte(byte in){ //Converts a byte into int as if it were unsigned
     return (int)in&0x7F+(in<0?128:0); 
    }
    
    void MakeBMP(String path,PImage img){  ///Grabs a path and an image, and saves it as an 8-bit BMP file with palette from dataPath("palette") file.
    
      int width=img.width;
      int height=img.height;
      //This is based on this: http://www.dragonwins.com/domains/getteched/bmp/bmpfileformat.htm
      byte[] test={66,77}; //                     0 "BM" header.
      test=append(test,byte2(0)); //              2 File size.  We are going to fill this later.
      test=append(test,byte2(0)); //              6 Reserved.          
      test=append(test,byte2(1024+54)); //10 Pixel data start offset. ok.
    
      test=append(test,byte2(40)); //            14 Header size.
      test=append(test,byte2(width)); //         18 Width.
      test=append(test,byte2(height)); //        22 Height.
      test=append(test,byte(1)); //              26 Amount of images in this image.  ._.
      test=append(test,byte(0)); //              27 2 bytes for amount of images here. ._.
      test=append(test,byte(8)); //              28 Bits per pixel. This is 8 bit bmp generator code thing, so not much to say here.
      test=append(test,byte(0)); //              29 Bits per pixel. Needs 2 bytes.
      test=append(test,byte2(0)); //             30 Compression type. I don't like compression.
      test=append(test,byte2(0)); //             34 Image size. Really matters only if compressed, so I'll just 0 here.
      test=append(test,byte2(0));//              38 Preferred X printing resolution. We aren't going to print sprites!
      test=append(test,byte2(0));//              42 Preferred Y printing resolution. We aren't going to print sprites!
      test=append(test,byte2(0));//              46 Number of used Color Map entries.
      test=append(test,byte2(0));//              50 Number of significant Color Map entries.
    
      //54 Color table now? Oh snap...
      byte[] Palette=loadBytes(dataPath("palette"));
      for(int i=54;i<54+1024;i++){          //Loop to iterate through the whole palette
      //test=append(test,byte(i-54)); //blue
      //test=append(test,byte(0)); //green
      //test=append(test,byte(0)); //red
      //test=append(test,byte(0)); //zero
        test=append(test,Palette[i]); //Just grab everything from the palette file
      }
    
      //1078 AND EVEN COLOR DATA?! OHH SNAAAAP!!
      for(int i=0;i<(width+4)*height;i++){ // Loop to iterate for every input image pixel  (+4 is because every horisontal line has 4 additional bytes at the end, which are usually all 0, I guess spacing different lines out.
      //This next bit of code grabs the color from the input image and compares it to all colors in the palette until it finds the one that is closest, and assigns that.
     color pixel=img.get(bitx(i,width),height-bity(i,width)-1); //Grab matching image pixel
     if(alpha(pixel)==255&bitgood(i,width)){ //If it's transparent just use first color in the palette (which is R G B 255 0 255 in the included palette). Or if it's a spacing byte then also don't bother.
         float diffbest=99999999; //How different is the best matching color in the palette from the current image pixel?
        int best=0;  //What color in the palette is the best matching to the current image pixel?
      for(int u=0;u<255;u++){ //Iterate through all palette colors
        float diff=abs(unsignbyte(test[56+u*4])-red(pixel))+abs(unsignbyte(test[55+u*4])-green(pixel))+abs(unsignbyte(test[54+u*4])-blue(pixel)); //Difference between current img pixel and current pal color
        if(diff<diffbest){ //If this palette color's better
         diffbest=diff; best=u;  //Remember it
        }
        if(diff==0){u=255;} //If it matches perfectly to the input image color, why bother with all of the other colors in the palette?
      }
      test=append(test,byte(best));
      }else{test=append(test, byte(0));} //Just 0.
    }
    
      test[2]=byte2(test.length-54)[0]; //       2 File size
      test[3]=byte2(test.length-54)[1]; //       2 File size
      test[4]=byte2(test.length-54)[2]; //       2 File size
      test[5]=byte2(test.length-54)[3]; //       2 File size
      saveBytes(path,test); //Allright, work's done! Everyone go home!
    }
    
  • int bitoffset(int width){
     return width+(width==1?1:max(0,3-((width-1)%4)));
    

    is this right? the first 10 values for this is

    0 4
    1 2
    2 4
    3 4
    4 4
    5 8
    6 8
    7 8
    8 8
    9 12
    

    which seems wrong (specifically the value for 1, which i guess is rare)

    return (((w - 1) / 4) + 1) * 4;
    

    is shorter and returns

    0 4 // slightly dodgy, i admit. invalid input?
    1 4
    2 4
    3 4
    4 4
    5 8
    6 8
    7 8
    8 8
    9 12
    
  • I don't know if its wrong or not, since I was bruteforcing that part heavily to find the right values for every possible image width, and then lazily came up with a formula that follows the sequence of numbers I found to work. Images with width of 1 were an exception to that sequence, so I had to put the width==1? part for that.

    The reasons for the complicated formula is because at first I excluded the "width+" at the start and brute forced the rest, which is why I was confused. And now, seeing that it's this simple kind of a pattern, I feel embarrassed. LOL

  • Also I just realized that instead of making header byte by byte, I could also copy it from the palette, then simply change file size and width/height values. That palette file I included even has the same header. Mess of a coder I am! :D

  • There also is a new forum

  • What forum? Where? When? Why? o_o

  • I don’t know 4 weeks or so

    https://discourse.processing.org/

    It’s also the Forum link of processing main web page

  • Oh. Thanks, good to know.

    Well, the idea that this exact message will end up as permanently archived very soon is at least fun...

  • I think they wait till here is no activity anymore and then archive it / set it passive

Sign In or Register to comment.