送信されてきたものを見る
では何か余計なものとは何だろう。
image1
が作成された後、入力ストリームからのデータを確認してみる。
import java.awt.image.BufferedImage; import java.io.*; import javax.imageio.ImageIO; public class GnuplotUser5 { private static final String GNUPLOT = "/usr/bin/gnuplot"; private static final int COLS = 16; private static final int ROWS = 16; public static void main(String[] args) throws IOException, InterruptedException { Process p = new ProcessBuilder(GNUPLOT, "-").start(); PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(p.getOutputStream()))); BufferedInputStream in = new BufferedInputStream(p.getInputStream()); out.println("set terminal png"); out.println("plot sin(x)"); out.println("set output"); out.flush(); BufferedImage image1 = ImageIO.read(in); System.err.println(image1); out.println("set terminal png"); out.println("plot sin(x+pi/10)"); out.println("set output"); out.flush(); loop: for (int n = 0; n < ROWS; n++) { for (int m = 0; m < COLS; m++) { int c; if (in.available() > 0 && (c = in.read()) != -1) { System.err.printf(" %02x", c); } else { if (m != 0) System.err.println(); break loop; } } System.err.println(); } out.close(); in.close(); p.waitFor(); } }
gnuplot側で二枚目の画像を作成しJava側に送信するところまでは前と同じだが、
ImageIO.read(in)
を使ってimage2
を作成せずに、
そのまま入力ストリームの中身をBufferedInputStream#read()
で1バイトずつ読んでいく。
ここでは最大で256バイト分読み、16バイト単位で16行表示してみた。
BufferedImage@1b0889a: type = 13 IndexColorModel: #pixelBits = 8 numComponents = 3 color space = java.awt.color.ICC_ColorSpace@d844a9 transparency = 1 transInde x = -1 has alpha = false isAlphaPre = false ByteInterleavedRaster: width = 640 height = 480 #numDataElements 1 dataOff[0] = 0 6d f7 3e af 00 00 00 00 49 45 4e 44 ae 42 60 82 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 02 80 00 00 01 e0 08 03 00 00 00 02 0f 2c d6 00 00 01 32 50 4c 54 45 ff ff ff 00 00 00 a0 a0 a0 ff 00 00 00 c0 00 00 80 ff c0 00 ff 00 ee ee c0 40 00 c8 c8 00 41 69 e1 ff c0 20 00 80 40 c0 80 ff 30 60 80 8b 00 00 40 80 00 ff 80 ff 7f ff d4 a5 2a 2a ff ff 00 40 e0 d0 00 00 00 1a 1a 1a 33 33 33 4d 4d 4d 66 66 66 7f 7f 7f 99 99 99 b3 b3 b3 c0 c0 c0 cc cc cc e5 e5 e5 ff ff ff f0 32 32 90 ee 90 ad d8 e6 f0 55 f0 e0 ff ff ee dd 82 ff b6 c1 af ee ee ff d7 00 00 ff 00 00 64 00 00 ff 7f 22 8b 22 2e 8b 57 00 00 ff 00 00 8b 19 19 70 00 00 80 00 00 cd 87 ce eb 00 ff ff ff 00 ff 00 ce d1 ff 14 93 ff 7f 50 f0 80 80 ff 45 00 fa 80 72 e9 96 7a f0 e6 8c bd b7 6b b8 86 0b f5
二枚目の画像もPNG形式で作成したはずだが、
送られてきたデータの初っ端が、
6d f7 3e af 00 00 00 00 ...
である。これはPNGの最初に現れるべきマジックナンバーとは全く異なる。
二行目を見てみると、
89 50 4e 47 0d 0a 1a 0a ...
これぞまさしくPNGの先頭部分のバイト列*1である。
つまり、16バイト分の余計なものが送られてきて、
これを画像データの開始部分として頑張って解釈しようとしたが、
努力むなしく当然失敗してnull
を返したということだと推測される。
一体この16バイト分のデータは何なのだろう。
最初の4バイトに続く12バイト分は、
... 00 00 00 00 49 45 4e 44 ae 42 60 82
である。これはPNGにおいて画像の終端を表すIENDチャンクまんまである。
ということは、送られてきたデータの最初の4バイト
6d f7 3e af ...
はIENDチャンクの一つ前のチャンクのCRC-32部分なのだろう。
つまり、image1 = ImageIO.read(in)
においてImageIO#read
は一枚目の画像データを読み込み切らず、
オブジェクト作成に必要な画像データではない残りの部分をそのまま入力ストリームに食べ残しているということだ。
まあ終端チャンクを読み残すだけであるのならまだ分からないでもないのだが、
画像データ(であろう)チャンクのCRC-32部分さえ読み残す動作はあまり品がよいとはいえないと思う。
データ誤りを見つけ出すせっかくの機会を捨てているわけなので。
ベリファイは実装せずとも、せめてチャンク単位でデータは消費しようよと思ってしまった。
ということで、二枚目の画像オブジェクトを作成できなかった原因は、
一枚目の画像に関わるバイト列の一部をImageIO#read
が入力ストリームに読み残していたことにあった。
*1:特に先頭の4バイトはShift_JISとして扱えばお馴染みの「臼NG」になる