This discussion is archived
13 Replies Latest reply: Apr 1, 2011 6:37 AM by 851713 RSS

Custom Image Filter

851713 Newbie
Currently Being Moderated
Hello,

I want to create my own image filter. This filter should look for each pixel
whether the red/blue/green-pixel-value is below 25 and if so set the alpha-vlaue to 0.
I want to make those pixel translucent.

I found some samples of custom filters but I am not familar with the shift operators ( < / << / ... )
So any ideas or tutorials?

Regards,

Olek

Edited by: 848710 on 30.03.2011 13:15
  • 1. Re: Custom Image Filter
    851329 Newbie
    Currently Being Moderated
    Olek,

    You are on the right track. The shift operators will let you get at the alpha and color values in a four-byte integer. Unlike a language like C, which would let you treat an array of integers as an array of bytes (or even records) if you wanted to, Java tends to keep you committed to treating integers like integers. In your image array, this means you will usually pack 8-bit alpha, red, green, and blue values, each in the range from 0 to 255, into single 32-bit values. The shift operators are how you move the eight bits of one value into the right place in the 32-bit integer. So, if you have four values, a, r, g, and b, you need to pack them into an integer like this:
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10| 9| 8| 7| 6| 5| 4| 3| 2| 1| 0|
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |           a           |           r           |           g           |           b           |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    So, your alpha value is the highest eight bits in the integer, your red value is next highest eight bits, green is the next highest, and blue is the lowest eight bits. To move any eight-bit value into the right place, you shift it by that number of bits. To combine the results of multiple shifts, you use the logical OR operation:
    int a, r, g, b;
    ...
    int i = (a << 24) | (r << 16) | (g << 8) | b;
    Notice I have used integers, not bytes, for the a, r, g, and b variables. That's because there is no unsigned byte data type in Java, and Java would sign-extend bytes with values "greater" than or equal to 0x80 (since those are actually negative numbers when stored in bytes). Thus:
    byte b = 0x80;
    int i = b;
    results in i being 0xFFFFFF80. In my example above, with all the bit-shifts and ORs, if b were 0x80, Java's sign-extension would treat it as 0xFFFFFF80, and that would again have all the bits to the left of the blue value set to 1.

    Now, to get the a, r, g, and b values back out of their packed format, you shift again, but the other direction:
    a = (i >> 24) & 0xFF;
    r = (i >> 16) & 0xFF;
    g = (i >>  8) & 0xFF;
    b =  i        & 0xFF;
    This time note that I have used the AND operator after shifting, to mask off the bits I don't want.

    Now, this may all seem clunky and awkward, especially if you are used to a language like C that allows pointers that can point to the same memory locations, but treat them as different data types. Indeed, I'd concede that it is clunky and awkward, but Java2D seems to work best with packed integers (even though its Image class does support other formats; but they play heck with your code in other ways). It is somewhat comforting, perhaps, to know that, if you dig into the routines in Java2D that provide access to individual colors, you find out that all this shifting and ORing and ANDing is exactly how the wizards at Sun do it, so it must be right. ;)

    To round this out, here's an SSCCE you can try:
    public class S
    {
         public static void main(String[] args)
         {
              int     a, r, g, b;
              int     i;
    
              a = 0xF0;
              r = 0xE0;
              g = 0xC0;
              b = 0x80;
    
              i = (a << 24) | (r << 16) | (g << 8) | b;
    
              System.out.printf("0x%08x\n", i);
    
              a = (i >> 24) & 0xFF;
              r = (i >> 16) & 0xFF;
              g = (i >>  8) & 0xFF;
              b =  i        & 0xFF;
    
              System.out.printf("0x%02x, 0x%02x, 0x%02x, 0x%02x\n", a, r, g, b);
         }
    }
    For more on the "bitwise" operators (as <<, >>, &, and | are sometimes called), see Chapter 3 of "Core Java," Volume I, 8'th edition.
  • 2. Re: Custom Image Filter
    morgalr Explorer
    Currently Being Moderated
    Using the shift is prefered due to speed, but you can also do "AND" and normal division to do the same thing:

    in an ARGB BufferedImage a getPixel will return 1 byte for the Alpha, 1 byte for the Red, one byte for the Green, and one byte for the blue.

    To set the Alpha to 0, you do:
    int myPixel = MyARGB.getRBB(myX, myY);
    myPixel = myPixel & 0x00FFFFFF; //hex value for binary mask to zero alpha and preserver RGB values
    MyARGB.setRGB(myX, myY, myPixel);
    To get Blue value:
    int myPixel = MyARGB.getRBB(myX, myY);
    int myBlue = myPixel & 0x000000FF; //mask off only Blue--least significant byte;
    To get Green value:
    int myPixel = MyARGB.getRBB(myX, myY);
    int myGreen = myPixel & 0x0000FF00; //mask off only Green--notice mask change;
    myGreen = myBlue/0x00000100; //devide by 256 (2^8) to shift the Green value right 1 byte--same as shifting 8 times
    To get Red value:
    int myPixel = MyARGB.getRBB(myX, myY);
    int myRed = myPixel & 0x00FF0000; //mask off only Red--notice mask change;
    myRed = myRed/0x00010000; //devide by 256*256 (65536 or 2^16) to shift the Red value right 2 bytes--same as shifting 16 times
    Even if you don't use this, it may help you see what is happening in the shift operator.

    You can also retrieve color codes this way:
    int myPixel = MyARGB.getRBB(myX, myY);
    Color myColor = new Color(myPixel);
    int myRED = myColor.getRed();
    int myGreen = myColor.getGreen();
    int myBlue = myColor.getBlue();
  • 3. Re: Custom Image Filter
    851713 Newbie
    Currently Being Moderated
    Hi again,

    Thank you very much Stevens Miller for your selfmade tutorial.
    Didn't expect this here ;)
    I hope that now I know the basics to write this filter based on the packing/unpacking using the shif operators.

    Thank you too morgalr!

    I have one more question:

    Can you describe the behavior of those hex-styled values like : 0x00FFFFFF

    If you use 00 you ignore this part - ok
    If you use FF you get the "complete" value of this part?

    What happend if you use intermediate values?

    best wishes,

    Olek
  • 4. Re: Custom Image Filter
    851329 Newbie
    Currently Being Moderated
    morgalr, those get/set methods are probably the best way to become familiar with image work under Java/Java2D, though you're also right about the speed. My rough tests indicate that "/ 256" takes about twelve times as long as ">> 8" takes. (Which surprises me, since 25 years ago, Unix experts were bragging to me that the stock C compiler recognized "/ 256" and compiled it into a shift, for optimization. I'd have expected javac to do it too, but apparently not. Indeed, maybe those experts were wrong.)

    Once the packing concepts are mastered, I'd expect the overhead in calling the get/set methods wouldn't make it worth it to use them. In fact, IIRC, it's those methods I looked at when I found that even the Java2D source uses shifts and masks, so you're ultimately just calling routines to do the same thing one line of code would do. But I think it's a good idea to show them to anyone who is new to working with raster image data. They are much easier to understand than the bit-twiddling is.
  • 5. Re: Custom Image Filter
    851713 Newbie
    Currently Being Moderated
    Re,

    Slowly I get it I guess ;)

    FF == this color 100% opaque or color value == 255 - isn't it?
    00 == this color 0% opaque or clolor value == 0

    I very often use CSS or Adobe Photoshop where I met those 0xXXXXXX values but
    as most people( again I guess ), are more familiar with RGB(A) then hex-colors.

    Greetings,

    Olek

    Edited by: 848710 on Mar 31, 2011 11:06 AM
  • 6. Re: Custom Image Filter
    morgalr Explorer
    Currently Being Moderated
    Olek,

    The idea of binary masking just goes back to binary "AND" and "OR" bit manipulation:

    1 and anything is anything: 1 and 1 is 1, 1 and 0 is 0 (general: 1 & X = X where X can be 0 or 1).
    0 and anything is 0: 0 and 1 is 0, 0 and 0 is 0 (general: 0 & X = 0 where X can be 0 or 1).

    So when you devise a mask, you have to think in binary: 8 bits, xxxxxxxx, and you can easliy represent that in Hexadecimal format or Hex. Hex is a base 16 number system and as such can represent number for each place holder from 0 to 15. This gives a problem with our decimal based system, 0 to 9, so we need something to represent 10, 11, 12, 13, 14, and 15 when using Hex. This problem was solved by extending the digits by adding the first 6 letters of the alphabet--A to F. You end up with a number system that has valid digits of 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, and F. Each digit of a Hex number can be represented in 1/2 byte or 4 bits xxxx.

    If you desire to "set" a bit, you use OR and a 1 value in that bit location.
    if you want to "reset" (clear) a bit you use a 0 in that bit location.
    if you want to leave it alone you use a 1 with an AND operation //not covered here.
    if you want to leave it alone you use a 0 with an OR operation //not covered here.

    for a simple example, lets look at your problem with an ARGB number:

    Let's say you get 428,1541,137 as your pixel value. I don't know about you, but I cannot invision what part of that is assigned to each part of the ARGB image pixel, so if we convert it to hex it becomes 0xFF332211 where the "0x" denotes that you are using a Hex based number. The FF or bit pattern 11111111 (decimal 255) controls our Alpha characteristics. 33 or bit pattern 00110011 (decimal 51) controls Red. 22 or bit pattern 00100010 (decimal 34) controls Green. And 11 bit pattern 00010001 (decimal 17) controls Blue. To get our original number back we have to multiply the various numbers by thier weights according to placement--just like in decimal, but using Hex. 255*256^3 (alpha) + 51 * 256^2 (Red) + 34 * 256^1 (Green) + 17 * 256^0 (Blue). I'll leave you to do the math.

    When you do a shift in Java or other language, you move the bit patter over to the left or right a known number of bits, this is what happens if you do integer division by 2. Notice to shift 8 places would be divide by 2 a total of 8 times or 2^8 or decimal 256 or the 5 bit bit patterm 10000 or hex 0x0100.

    To clear or reset the bits in the alpha component you need to use zeros to clear with an AND operation (0 and anything is 0), but you want to leave the other bits intact so you have to use a 1 with the AND operation (1 and anything is your original "anything"). So you get:

    0xFF332211 & 0x00FFFFFF

    which will give you 0x00332211 //all alpha set to 0 and intact color values.

    Even simpler example 3 AND 5 = ?

    look at the bit patterns

    00000011 (3)
    00000101 (5)

    When you and those you get 00000001 (decimal 1).

    0 AND 0 is 0
    0 AND 0 is 0
    0 AND 0 is 0
    0 AND 0 is 0
    0 AND 0 is 0
    0 AND 1 is 0
    1 AND 0 is 0
    1 AND 1 is 1

    I hope this helps you in your visualization.
  • 7. Re: Custom Image Filter
    851329 Newbie
    Currently Being Moderated
    Olek,

    The hex values in my sample program serve two different purposes. In this part
    a = 0xF0;
    r = 0xE0;
    g = 0xC0;
    b = 0x80;
    they are the actual values for alpha, red, green, and blue. I always use hex for that because it is easy to figure out how to add another 25% here (0x40) or another 12.5% there (0x20). I chose those four values because they were distinct, easy to recognize after being packed and then unpacked, and all were guaranteed to sign-extend if treated as bytes. You are quite correct that hex values like these get used in other settings, like CSS, to specify colors. Because each pair of hex digits represents a distinct byte, it's a very simple way to specify a color in a single hex number. In my example, that number would just be 0xE0C080 (or, if you included the alpha channel, 0xF0E0C080).

    Now, when it is necessary to strip off (people often say "mask") the bits you don't want from a packed four-byte integer, the hex numbers are serving a different purpose. When you use the AND operator (the "&" character), it actually operates at the level of the individual binary digits (the "bits") to produce a result. Suppose we want to get just the green value from the packed integer in my example. The integer is 0xF0E0C080. In binary, that's this:
    11110000111000001100000010000000
    To get the green part, you need to shift these bits eight places to the right, with ">> 8." That gives you this result:
    11111111111100001110000011000000
    A number of interesting features appear in that result. First, notice that the eight bits on the right end of the original number are gone. They get lost as a result of the shift operation. The right-most 24 bits of the result are exactly the same as the original left-most 24 bits of the number before the shift operation. That's literally what "shift" does: it moves the bits. But, most interestingly of all (perhaps) are the left-most eight bits of the result. Notice they are all "1" bits. That's because the right-shift operator (the ">>") sign-extends its result. The effect is that a negative number produces a negative result. In Java, this is always how the right-shift operator works. In other languages, including C, it's not always specified. Some C compilers will sign-extend right-shifted negative numbers, and some won't. It's up to the compiler writers to decide. In Java, it's not up to the compiler writers. The ">>" always sign-extends. Whether or not right-shifts should extend signs has been known to keep some programmers arguing for hours and hours. These types of debates are called "religious" arguments, which usually means they are not worth having. As I am a Unitarian Universalist, and my church has taken no position on sign-extension in right-shifts, I don't care one way or the other. To avoid needing to know, however, I always just use a mask to get only the bits I want, which I will explain in a second. (But, you should know, Java includes an unsigned right-shift operator, ">>>," so you have final say in your own code as to whether there will be sign-extension or not.) All of this comes from the fact that Java, C, and most languages represent signed integers with "two's-complement" arithmetic, which is a kind of numerical magic you don't want to have learn much about if you don't have to.

    Now, we wanted the green byte, right? It started out packed into our four-byte integer as the ninth, tenth, eleventh, twelfth, thirteenth, fourteenth, fifteenth, and sixteenth bits, starting with the first bit as the one on the right. By shifting our integer eight bits to the right, our green byte is now in the first, second, third, fourth, fifth, sixth, seventh, and eighth bits. (Beware that most people number bits the same way that you use an array index, by calling the first one "bit zero," the second one "bit one," and so on). In my code, I masked off the bits I didn't want, and kept the bits I did want, with one of those hex values: 0xFF. The operation was this:
    g = (i >> 8) & 0xFF;
    To understand what that does, you need again to see it in binary. The "i >> 8" part results in the 32 bits I wrote out above. The "0xFF" part is another 32-bit number, the first 24 of which are all "0," and the last eight of which are all "1" (Java conveniently treats hard-coded constants like integers, so 0xFF is treated like a 32-bit number, the same as if we had written "0x000000FF"). So, in binary, i >> 8 and 0xFF look like this:
    11111111111100001110000011000000
    00000000000000000000000011111111
    Finally, the AND operator works on the matching bits in the two values above. That is, the left-most bit of each is matched with the left-most bit of the other, the second left-most bit is matched with the other's second left-most bit, and so on. For each matched pair of bits, if one of the bits is "1" and the other bit is "1," the matching bit in the result is "1." In any other case, the result is "0." So, "(i >> 8) & 0xFF" results in this:
    00000000000000000000000011000000
    In hex, that's "0xC0," which is our green value, which is what we wanted.

    So, in some cases, my hex values were providing actual numbers, to set the values of alpha and the color channels. In other cases, they are serving as masks, to get rid of the bits I don't want and leave behind only the ones I do want, after shifting, to unpack those alpha and color values when I have them in packed form.

    Hope that helps.
  • 8. Re: Custom Image Filter
    851713 Newbie
    Currently Being Moderated
    Re,

    Thanks again both for your outstanding tutorials.

    I am familiar with bitwise logic since I am a college student ( bioinformatics ).
    So shame on me ;)
    Ok taking into account, that this special color-management was not part of basic lectures.

    Now I wrote the filter. It works but not that nice as I suggested.

    The scenario is the following :

    I got an image where I paint some stuff on it. Next I use a bright pass filter ( from the book filthy rich clients )
    on this image. After this each pixel of the resultig pixel is full opaque.
    What I want is that I overlay this image over an other.

    With my current filter the result looks not very smooth.
    The trick is maybe how to setup the alpha value of thos pixels who are fairly dark.

    Here is my code
    private void filterImage( int[] pixels, int width, int height ) {
    
            // color values for alpha, red, green and blue
            int a = 0;
            int r = 0;
            int g = 0;
            int b = 0;
    
            int index = 0;
            for ( int y = 0; y < height; y++ ) {
                for ( int x = 0; x < width; x++ ) {
                    int pixel = pixels[index];
    
    
                    // unpack the pixel's components
                    a = ( pixel >> 24 ) & 0xFF;
                    r = ( pixel >> 16 ) & 0xFF;
                    g = ( pixel >> 8 ) & 0xFF;
                    b = pixel & 0xFF;
    
                    int maxVal = Math.max( Math.max( r, g), b );
    
                    // check if the max value of r/g/b is below the threshold
                    if ( maxVal < threshold ) {
                        // set the alpha value to the max value
                        a = Integer.parseInt( Integer.toHexString( maxVal ), 16 );
                        
                        // pack the four color values into one pixel
                        int translucentPixel = ( a << 24 ) | ( r << 16 ) | ( g << 8 ) | b;
                        // and set the new value
                        pixels[index] = translucentPixel;
                    }
    
                    //System.out.println( "Red : " + r + " Green " + g + " Blue " + b );
    
                    index++;
                }
            }
        }
    The results are fairly different for some thresholds.

    Again, thanks a lot,

    Olek
  • 9. Re: Custom Image Filter
    851329 Newbie
    Currently Being Moderated
    Hmmm... In your original post here, you said you wanted to set the alpha value to zero if the RGB values were below a threshold. Here, though, you're setting alpha to the maximum value of the RGB channels. By the way, this line
    a = Integer.parseInt( Integer.toHexString( maxVal ), 16 );
    is the same as this line
    a = maxVal;
    But, if you really want to set alpha to zero, use this, of course:
    a = 0;
    Or, more simply, just don't shift anything into those bits when you pack your ARGB integer, like this:
    int translucentPixel = ( r << 16 ) | ( g << 8 ) | b;
    Something tells me that's not what you're really trying to do, however. Seting alpha to zero completely hides the pixel. It won't make any change to your final image. If you're trying to blend the image smoothly, so that anything at the threshold shows in full color, anything at about half the threshold shows in about half color, and anything close to zero shows almost nothing, you need to scale your alpha value by maxVal, with something like this:
    a = (255 * maxVal) / threshold;
    I'm not sure if that's what you want, but you might try it and, even if it isn't what you want, it might give you some further ideas that will get you closer.

    Can you explain a bit more of what it is you're trying to accomplish?
  • 10. Re: Custom Image Filter
    851713 Newbie
    Currently Being Moderated
    Hi agian,
    Stevens Miller wrote:
    Hmmm... In your original post here, you said you wanted to set the alpha value to zero if the RGB values were below a threshold. Here, though, you're setting alpha to the maximum value of the RGB channels. By the way, this line
    a = Integer.parseInt( Integer.toHexString( maxVal ), 16 );
    is the same as this line
    a = maxVal;
    Thank you - I am still not familiar with hex-values.
    But, if you really want to set alpha to zero, use this, of course:
    a = 0;
    True, but I realized, that this looks not smooth, so I try to play with the alpha value.
    Or, more simply, just don't shift anything into those bits when you pack your ARGB integer, like this:
    int translucentPixel = ( r << 16 ) | ( g << 8 ) | b;
    Ok, thats nice too.
    Something tells me that's not what you're really trying to do, however. Seting alpha to zero completely hides the pixel. It won't make any change to your final image. If you're trying to blend the image smoothly, so that anything at the threshold shows in full color, anything at about half the threshold shows in about half color, and anything close to zero shows almost nothing, you need to scale your alpha value by maxVal, with something like this:
    a = (255 * maxVal) / threshold;
    I'm not sure if that's what you want, but you might try it and, even if it isn't what you want, it might give you some further ideas that will get you closer.

    Can you explain a bit more of what it is you're trying to accomplish?
    Yes of course.

    What I got after the bright pass filter is an image with, lets say some text on it. The text is soft glowing around the edges of the text.
    Therefore I first paint the brightpassed image with the text, then I draw on top of this image the same text without the filter.
    Now it looks nice. But I want to add some nice looking background.
    Here is the point where I want to change the opacity of those pixel which are nearly black, but maybe this is not enough.
    Maybe I got to change for all pixel of the merged image ( bright pass filtered + normal image ) the alpha value to
    get a nice - smooth - looking result if I paint the merged image on top of an other image.

    Thanks again for your helpfull explainations.

    Best wishes,

    Olek

    Edited by: Olek_ on 31.03.2011 13:22
  • 11. Re: Custom Image Filter
    851329 Newbie
    Currently Being Moderated
    Olek,

    Okay, thanks to your last description, I think I know what you're trying to accomplish, graphically speaking. And I think you're running into a conceptual problem that affects a lot of people when they try to composite two or more images. It may be that you're confusing the glowing effect you're seeing around your text with a mask (not the same kind of mask I wrote about in Java). I'd suggest you try my scaled alpha trick first and see if that gets you closer, but I think you might want to hold off writing more code for now and play with a good, versatile imaging tool like the Gnu Image Manipulator Program. That's something of an Adobe Photoshop work-alike, but it's free. (Of course, if you have or want to buy Photoshop, that's a good one too). You can get it at www.gimp.org. I'd suggest working with that for a while and getting fully familiar with image compositing. I've done a lot of this and I can tell you it's not obvious at all, at first, how you get the blending you're looking for. Once you get it figured out, it will seem very straightforward, but it takes one of those "a-ha!" moments that go with things you study for a long time before they're clear, like infinite series with finite sums and special relativity. Just keep at it and you'll get it. But, seriously, I recommend working more with the graphic side for now, and return to writing code when you can define precisely for yourself what your code has to do. Right now, I think you're laboring to master two sets of concepts (one being graphics, the other being Java programming) when you could be working on just one of them.

    Best of luck!
  • 12. Re: Custom Image Filter
    morgalr Explorer
    Currently Being Moderated
    Olek,

    Generally speaking what you want to do now is mask your image. Choose everything that is not your Text and Effect and turn it transparent. You can then write that over another ARGB image. You'll get your background image with your text laying over the top and no other interaction between the 2 images.
  • 13. Re: Custom Image Filter
    851713 Newbie
    Currently Being Moderated
    Hello Mr. Steven Millers,

    Thanks again for your great help!

    your approach using
    a = (255 * maxVal) / threshold;
    Looks much better and nearly in the way I want to.

    I know GIMP and I use it on my Linux-distribution too.
    What I not want is to reproduce the functionality of Photoshop or GIMP. I am far beyond this scope.
    I only want to create some nice looking gui using everything Java2D can provide.

    The reason I want to compute this images by Java instead of paint some nice images in Photoshop/GIMP is,
    that I can't scale such "finished" images. But if I create them by myself I can.

    On the other hand it is a nice and exciting work for me.

    I use the SwingX-library too, which provides nice BlendComposites also described in the great book "Filthy rich clients" by Chet Hase and Romain Guy.

    If you got more suggestions let me hear!

    Maybe I can use AlphaComposite too for those kinds of problem?

    @morgalr :

    Yes it is a mask-thing. Now it works nice for the moment.
    Thank you too.

    Greetings,

    Olek

Legend

  • Correct Answers - 10 points
  • Helpful Answers - 5 points