プログラムdeタマゴ

nodamushiの著作物は、文章、画像、プログラムにかかわらず全てUnlicenseです

Java画像処理をはじめよう。初めての画像処理(・∀・)

 というわけで、なんでか知らんけどJavaで画像を弄らんといけなくなった。手っ取り早く画像処理をしてみたい。そんな人のために書くことにしたよ。
 一応シリーズにするつもり。もちろん、気が向いたときにしか更新しないけど。最終的にどこまで踏み込んだ内容まで行くのかわかんないけど、最初っからいつもみたいな意味不明な文章で飛ばしたりするようなことはしないよ。
 こういう初歩的な内容って、頭使わなくていいから、私の精神安定剤になるのだよ………wスウチカイセキモウイヤダ
 語尾の「よ」はそろそろやめるお。

・内容
 Java2Dを使わずに、直接ピクセルデータを弄って画像を処理する。

・対象
 Javaが読めればOK




色について

 色について簡単なお話をしましょう。
 モニタで扱う色はRGB形式といって、赤と緑と青の光を加法混色することで色を作っています。ただ単にRGBといってもsRGBやAdobeRGBといった種類があって、表現できる色空間が異なります。でも、基本となる赤と緑と青の光で色を表すと言うことは同じだよ。

 ついでですが、人間の視覚系を大体表した色空間はXYZ表色系があります。この色空間は実際に人を使って実験しながらつくられたものだよ。
 XYZも元はと言うとRGB形式と考え方は同じように作られています。ただし、RGBは加法混色しか考えてないけど、XYZでは減法混色(光の引き算)を考慮しています。測定方法は、赤、青、緑の光の光源と、ある色を発する光源の4つを用意し、赤、青、緑の光源の強弱を調整しながら残りの色と同じ色を作る、という測定方法。ただし、単純に赤、青、緑を重ねても同じ色が作れそうもないときは、残りの色の方に色を重ねることでマイナスの明かりを表すことにします。この測定で負値を含むRGBの3次元色空間ができます。このままでは取り扱いにくいので、負値が出ないように二次元平面に座標変換した物がXYZ表色系になります。
 細かいことはこのページを読むと良いかな。

 いつかもっと深く踏み込んだ所まで書いてみたいけど、何せグラフやら図を用意するのが大変だからやらないだろうなぁ。



Javaで色の取り扱い

 JavaではsRGBが基本です。今後はsRGBをRGBといいます。RGBに透明度を足したARGBもあります。 (RGBAとも)
 (A)RGBのそれぞれの色のことを「チャネル」とこの記事ではいいます。
 各チャネルは8bitのサイズが基本です。8bitなので表せる色数は0〜255の256色です。
 これをbyte型で一つ一つ扱うこともありますが、多くの場合(A)RGBが一つのint型(32bit)データに詰められて扱うことが多いです。並びは多くは(A)RGBです。
 00000000 00000000 00000000 00000000
 32bitの中にこのようにデータが詰め込まれています。一番上のAはあったりなかったりです。なので、それぞれのチャネルの値を取り出すには次のようにします。

Alpha color>>>24
Red color>>16&0xff
Green color>> 8&0xff
Blue color &0xff

 逆にint型にデータを納めるときは

  • a<<24 | r <<16 | g << 8 | b

 となります。これらの取り出し、書き込みを毎回書くと非常に読みにくいので、全部関数にしておきましょう。以下のImageUtilityをstatic importすれば簡単に取り扱い、見やすいコードを書くことができます。
 Javaはインライン展開を積極的に行うので、関数にしても実行時に速度低下を招いたりはしません。

package img;
public class ImageUtility{
    public static int a(int c){
        return c>>>24;
    }
    public static int r(int c){
        return c>>16&0xff;
    }
    public static int g(int c){
        return c>>8&0xff;
    }
    public static int b(int c){
        return c&0xff;
    }
    public static int rgb(int r,int g,int b){
        return 0xff000000 | r <<16 | g <<8 | b;
    }
    public static int argb(int a,int r,int g,int b){
        return a<<24 | r <<16 | g <<8 | b;
    }
}

Eclipseを使う場合だったら、コンテンツ・アシストのお気に入りにこのクラスを登録しておくといいです。


BufferedImage

 では、ようやくJavaで画像を取り扱ってみましょう。
 基本的な画像はBufferedImageという画像になります。BufferedImageはJavaの画像で唯一ユーザーがピクセルデータにアクセスすることができる画像です。JavaFXでも早くBufferedImage相当の画像クラスできないかなぁ………。
 画像の生成方法は

  • new BufferedImage(幅, 高さ,BufferedImage.TYPE_INT_RGB)
  • new BufferedImage(幅, 高さ,BufferedImage.TYPE_INT_ARGB)

になります。前者がRGB形式、後者が不透明度を加えたARGB形式になります。
 ファイルとして保存されている画像を読み込みたいときはjavax.imageio.ImageIOを使います。

BufferedImage read;
try{
    read = ImageIO.read(File);
}catch(IOException e){
//読み込みもしくはデコードエラー
}

 Fileはfileオブジェクトです。逆に保存するときはwrite関数を使います。

BufferedImage write;//用意されている
try{
    ImageIO.write(write,"png",File);
}catch(IOException e){
//何らかのエラーが発生
}

 JPEG形式で保存したいときは"png"を"jpg"に変えます。Fileの拡張子も合わせることを忘れずに。保存するときにメタデータを挿入することもできますが、それはまた何時か別の記事に書きましょう。



BufferedImageから色の取得、設定

 BufferedImageから色を取り出し、設定するには各々

  • BufferedImage.getRGB(x,y)
  • BufferedImage.setRGB(x,y,argb)

 のメソッドを使います。x,yは取得、設定するピクセルの位置です。



簡単な画像処理!

 さて、そろそろ今回は終わりにしようと思ったのですが、画像処理の画の字も触れていません。これではまずい。
 というわけで、ネガポジ変換をしてみましょう。これは単純に各チャネルの値を255から引いた値に設定するだけです。

import static img.ImageUtility.*;

import java.awt.image.BufferedImage;
import java.io.*;
import javax.imageio.ImageIO;
public class Test {
    public static void main(String[] args) throws IOException {
        File f = new File("test.jpg");
        BufferedImage read=ImageIO.read(f);
        int w = read.getWidth(),h=read.getHeight();
        BufferedImage write =
                new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
        
        for(int y=0;y<h;y++){
            for(int x=0;x<w;x++){
                int c = read.getRGB(x, y);
                int r = 255-r(c);
                int g = 255-g(c);
                int b = 255-b(c);
                int rgb = rgb(r,g,b);
                write.setRGB(x,y,rgb);
            }
        }
        
        File f2 = new File("ret.jpg");
        ImageIO.write(write, "jpg", f2);
    }
}