JAVA: Image and Pixel Processing Part II
Java
222
White

Nobody viết ngày 31/12/2021

PART II

I have always mentioned that a good developer is the one who knows how to write the most efficient codes, not the one who writes the most beautiful codes. CS is mathematics. Mathematics can be very slow or very fast. Because CS is binary mathematics all CS operations are binary-oriented. Human mathematics bases on the decimal system. If you think as a human on working with computer your work becomes insufficient and erroneous. Even when we do some math we spend more time with Multiplication and Division than with Addition or Subtraction. And if we see two things that look equal we usually conclude that they are equal without having to do a subtraction in order to find out whether they are equal (result 0) or not. Such a conclusion is a logical reasoning. A logical reasoning is usually much faster than a subtraction. Similar to that logical operations on computer are much faster than mathematical operations. Let compare the following lines:

// logical operation (hard to understand):
p = (a << 24) | (r << 16) | (g << 8) | b;
// mathematical operation (easy to understand)
p = (a * 0x1000000) + (r * 0x10000) + (g * 0x100) + b;

Both yield the same result, but the logical operation is much faster than the mathematical operation. If you don't believe me you could consult, for example, the Clock cycles per Instruction of SHIFT-LEFT ( << ) and MUL ( * ), SHIFT-RIGHT ( >> ) and DIV ( : ), OR ( | ) and ADD ( + ) of Intel or AMD.

In many cases you can replace a math. operation with a logical one. For example: a * (2 ** n) can be replaced with a << n or a / (2 ** n) with a >> n. Because an image usually consists of thousands or millions of pixels (mega pixels) a loop with millions of logical operations is perceivably faster than the same loop with math. operations. An example with the Bitcoin.png or BigOnion.jpg

public class Pixels {
  public static void main(String... a) throws Exception {
    BufferedImage img = ImageIO.read(new File(a[0]));
    int width  =  img.getWidth();
    int height = img.getHeight();
    System.out.println(a[0]+" has a Width:"+width+", a Height:"+height+" and "+(width*height)+" pixels");
  }
}

and the output

C:\JFX\Color>javac -g:none -d ./classes Pixels.java

C:\JFX\Color>java Pixels ./images/Bitcoin.png
./images/Bitcoin.png has a Width:467, a Height:376 and 175592 pixels

C:\JFX\Color>java Pixels ./images/BigOnion.jpg
./images/BigOnion.jpg has a Width:850, a Height:566 and 481100 pixels

C:\JFX\Color>

To process a loop of 175 K or 480 K pixels it is the matter of efficiency, not of beauty.

I have to tell you a hilarious story. In 2014 I had interviewed two applicants for 2 jobs as Senior Developer (who was responsible for a project) and as Developer. What I didn't know that the two came from the same company and one of them was the boss of the other. I raised a question about the best way to process the pixels of an image. The ex-boss was proud of his 5 years experience while the other had only 3 years of experience. The ex-boss gave me the solution with "p = (a * 0x1000000) + (r * 0x10000) + (g * 0x100) +b" and argued about the readability for the laymen, while the other came with "p = (a << 24) | (r << 16) | (g << 8) | b" and argued that the response time (performance) was more important than the beauty and the users usually wouldn't care about the beauty of the codes. The senior and project leader job went to the one with "less experience", but more knowledge about Computer Science. On the day they came to work I perceived that something went wrong with the two. A week later I got the immediate resignation of the ex-boss without any reason, even the paying was better than his ex job. And then the new senior developer came to me and told me about the emerging problems between him and his ex-boss who couldn't stand with the reality that his ex-subordinate was now his boss.

The old story is still actual and you, as user, still confront the sluggishness of scrolling an online paper full of images. Probably because the "developers" were keener of beautiful than efficient codes, and at the expense of the viewers (or users).

With the X-Y coordinates we can address any pixel within an image and modify it at our will. For example, we want to change the color of a string on the picture from black to cyan. The image is "HelloWorld.png" with the string "Hello World and Joe" in black.

    BufferedImage inImg = ImageIO.read(new File(inFile));
    int width  = (int) inImg.getWidth();
    int height = (int) inImg.getHeight();
    //
    int RED   = (0xFF0000 & old_color) >> 16;
    int GREEN = (0xFF00 & old_color) >> 8;
    int BLUE  = (0xFF & old_color);
    //
    int RED_n   = (0xFF0000 & new_color) >> 16;
    int GREEN_n = (0xFF00 & new_color) >> 8;
    int BLUE_n  = (0xFF & new_color);
    //
    int[] pixels = inImg.getRGB(0, 0, width, height, null, 0, width);
    for (int y = 0, i = 0; y < height; ++y)
    for (int x = 0; x < width; ++x) {
      // a Pixel is an ARGB
      int alpha = pixels[i] & 0xFF000000;
      int red   = (pixels[i] >> 16) & 0xFF;
      int green = (pixels[i] >> 8) & 0xFF;
      int blue  =  pixels[i++] & 0xFF;
      if (RED == red && GREEN == green && BLUE == blue) {
        red   = RED_n;
        green = GREEN_n;
        blue  = BLUE_n;
      }
      inImg.setRGB(x, y, alpha | red << 16 | green << 8 | blue);
    }

alt text
With the partition of a PIXEL into 3 color elements RED, GREEN and BLUE we can (theoretically) manipulate one of the three or all. The ReplaceColor loop can be optimized as following if all 3 color elements are to be replaced:

    BufferedImage inImg = ImageIO.read(new File(inFile));
    int width  = (int) inImg.getWidth();
    int height = (int) inImg.getHeight();
    //
    int oldRGB = 0x00FFFFFF & old_color; // ignore the ALPHA
    int newRGB = 0x00FFFFFF & new_color; // ignore the ALPHA
    //
    int[] pixels = inImg.getRGB(0, 0, width, height, null, 0, width);
    for (int y = 0, i = 0; y < height; ++y)
    for (int x = 0; x < width; ++x) {
      // a Pixel is an ARGB
      int alpha = pixels[i] & 0xFF000000;
      int rgb   = pixels[i++] & 0x00FFFFFF;
      if (oldRGB == rgb) rgb = newRGB;
      inImg.setRGB(x, y, alpha | rgb);
    }

The loop is now neater and more efficient than the original loop. Suppose that we want to change only the GREEN element of all Pixels (or all RGB) then we have to dive into the RGB elements:

    // replace only the GREEN element
    BufferedImage inImg = ImageIO.read(new File(imgFile));
    int width  = (int) inImg.getWidth();
    int height = (int) inImg.getHeight();
    //
    int GREEN   = 0xFF00 & old_color;
    int GREEN_n = 0xFF00 & new_color;
    //
    int[] pixels = inImg.getRGB(0, 0, width, height, null, 0, width);
    for (int y = 0, i = 0; y < height; ++y)
    for (int x = 0; x < width; ++x, ++i) {
      // a Pixel is an ARGB
      int rgb   = pixels[i] & 0xFFFF00FF;
      int green = pixels[i] & 0xFF00;
      if (GREEN == green) green = GREEN_n;
      inImg.setRGB(x, y, rgb | green);
    }

If we want to color an image in only ONE color out of the three elements we need only to turn OFF the unneeded color elements (here: RED and BLUE to achieve the GREEN image).

    BufferedImage img = ImageIO.read(new File(imgFile));
    int width  = img.getWidth();
    int height = img.getHeight();
    //convert to greenscale
    for(int y = 0; y < height; ++y)
    for(int x = 0; x < width; ++x) {
      img.setRGB(x, y, img.getRGB(x,y) & 0xFF00FF00); // turn off RED and BLUE
    }

alt text

Similar to the color elements we can change the ALPHA value of any specified pixel, too. The ALPHA value determines the opaqueness (100% by 0xFF) or the translucency (or transparency - 100% by 0x00). Example:

    BufferedImage img = ImageIO.read(new File(imgFile));
    int width  = img.getWidth();
    int height = img.getHeight();
    //
    BufferedImage bImg = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
    int RGB   = pixel & 0x00FFFFFF;  // 100% translucent
    int[] pixels  = img.getRGB(0, 0, width, height, null, 0, width);
    for(int y = 0, i = 0; y < height; y++)
    for(int x = 0, l = 0; x < width; ++x, ++i) {
      int rgb   = pixels[i] & 0x00FFFFFF;
      if (rgb == RGB) bImg.setRGB(x, y, rgb); // 100% transparent
      else bImg.setRGB(x, y,  pixels[i]); // let it be
    }

The invocation:

// all BLACK pixels: 0x000000, tolerant +/- 0x10, source: FataMorgana.png
bImg = ImageTools.transparentColor(ImageTools.toInt(0x000000, 0x10, "FataMorgana.png");

ALL BLACK pixels are now so translucent that the background color (vary via the Slider: here is light blue) becomes visible.
alt text

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Nobody

7 bài viết.
482 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Bài viết liên quan
White
2 0
Trong bài viết này, một số hình ảnh hoặc nọi dung có thể bị thiếu do quá trình chế bản. Vui lòng xem nội dung ở blog gốc sau: (Link) (Link), chúng...
programmerit viết hơn 6 năm trước
2 0
White
0 0
Giới thiệu Trong bài hôm nay chúng ta sẽ tìm hiểu cách handle request POST của Spring Boot. Trước đó, bạn nên biết 1. 「Spring Boot 8」Tạo Web He...
https://loda.me viết hơn 2 năm trước
0 0
Male avatar
0 0
https://grokonez.com/deployment/vultr/howtoinstalljavainubunturemoteservervutrhostingvpsexample How to install Java on Ubuntu Remote Server – Vutr...
loveprogramming viết 9 tháng trước
0 0
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
7 bài viết.
482 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!