→ tkcn:code 更新了,剛剛建立 Stream 的順序錯誤,會導致 deadlock 12/30 22:22
推 ogamenewbie:ImageIO.read(in)改成(BufferedImage)in.readObject() 12/31 08:32
→ ogamenewbie:試試看? 12/31 08:33
→ tkcn:BufferedImage 應該不能 Serialize 吧? 12/31 08:48
推 ogamenewbie:我試試看弄個 class imp ser 存ImageIO write的byte[] 12/31 09:58
→ ogamenewbie:先說好, 所有意外狀況全部都沒有預防... 12/31 10:38
→ tkcn:這支可以 work, 感謝。 不過還是很好奇為什麼原本的會有問題 12/31 10:46
> -------------------------------------------------------------------------- <
作者: tkcn (小安) 看板: java
標題: Re: [問題] ObjectOutputStream + ImageIO 出現的問題
時間: Thu Dec 31 11:16:48 2009
: ◆ From: 220.132.160.117
: ※ 編輯: tkcn 來自: 220.132.160.117 (12/30 22:21)
: → tkcn:code 更新了,剛剛建立 Stream 的順序錯誤,會導致 deadlock 12/30 22:22
: 推 ogamenewbie:ImageIO.read(in)改成(BufferedImage)in.readObject() 12/31 08:32
: → ogamenewbie:試試看? 12/31 08:33
: → tkcn:BufferedImage 應該不能 Serialize 吧? 12/31 08:48
: 推 ogamenewbie:我試試看弄個 class imp ser 存ImageIO write的byte[] 12/31 09:58
: 推 ogamenewbie:http://paste.plurk.com/show/121974/ 試試看? 12/31 10:37
: → ogamenewbie:先說好, 所有意外狀況全部都沒有預防... 12/31 10:38
: → tkcn:這支可以 work, 感謝。 不過還是很好奇為什麼原本的會有問題 12/31 10:46
看了一下 ImageIO.write(OutputStream) 的 source,
這方法會自動產生 ImageOutputStream,
所以真正送出去的 Stream 就變成:
ImageOutputStream -> ObjectOutputStream -> SocketOutputStream
我猜測可能是因為 ImageOutputStream 和 ObjectOutputStream 都有自己的 header,
當第二次在 ObjectOutputStream 上建立 ImageOutputStream 時,
就會寫入那多餘的 16-byte。
這樣也可以說明為什麼不用 ObjectOutputStream 就不會有問題。
---
我剛剛在測試的時候也有試過利用 ByteArrayOutputStream,
不過沒有額外包成一個 class,結果還是失敗。
也有試過直接把 png file 的 data 丟進 ObjectOutputStream,
( png file 當初也是用 ImageIO.write() 寫入的,並且我也在接收方檢查過 header )
結果接收方還是沒辦法用 ImageIO.read() 接下來。
( javax.imageio.IIOException: Unknown row filter type )
可能要晚點再繼續做測試了。 (用跨年夜測 = =)
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 140.122.183.199
※ 編輯: tkcn 來自: 140.122.183.199 (12/31 11:20)
推 ogamenewbie:Object 的是有 header 12/31 12:00
推 sbrhsieh:tkcn 目前的做法即使不使用 ObjectIOStream 直接使用 12/31 16:42
→ sbrhsieh:SocketStream 應該也是會有不正確作動吧。 12/31 16:50
→ tkcn:剛剛測試過了,如 sbrhsieh 所說,確實有問題 12/31 17:06
推 Maisky:BufferedImage不是Serializable,傳不對不奇怪啊 01/01 08:36
→ tkcn:呃,是用 ImageIO 送的,跟 Serializable 無關 01/01 11:19
推 Maisky:就是因為不是serializable所以不保證送的對啊 01/01 22:02
> -------------------------------------------------------------------------- <
作者: tkcn (小安) 看板: java
標題: Re: [問題] ObjectOutputStream + ImageIO 出現的問題
時間: Fri Jan 1 21:23:58 2010
終於把這個問題弄清楚了。
讓我從實驗的步驟開始說明吧。
---
1. 為了要方便觀察接收到的封包,
所以我做了一張只有 1x1 的圖片,
接著再用程式把這張圖片讀進去再重新寫成圖片。
這張圖片的 16 進位 data 如下: (69-byte)
89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52
00 00 00 01 00 00 00 01 08 02 00 00 00 90 77 53
DE 00 00 00 0C 49 44 41 54 78 DA 63 F8 FF FF 3F
00 05 FE 02 FE 33 12 95 14 00 00 00 00 49 45 4E
44 AE 42 60 82
2. 從傳送端用 ImageIO.write() 送出這張圖片,
接收端會收到完全相同的 data。
3. 傳送端連續傳送兩次此圖片時,
也只是相同的 data 重複兩次,
並沒有看到我所預期的...多出來的 16-byte。
於是我又讓接收端用 ImageIO.read(),
結果還是一樣,第 1 張沒問題,第 2 張就會失敗。
4. 實驗做到這裡其實就可以看出端倪了,
剩下來都只是些驗證了,我就直接講結論吧。
ImageIO.write() 會寫出 69 byte,
而 ImageIO.read() 只會讀入 53 byte,
我試著把原始的 png 檔案只留下前 53 byte,(或著是隨意竄改最後 16-byte)
這個時候秀圖軟體是沒辦法開啟這個檔案的,
但是用 ImageIO.read() 還是可以正常的讀入圖片。
我猜測最後的 16-byte 應該是 png 格式的 check sum 之類的東西吧,
而 ImageIO.read() 並沒有做這樣的驗證,甚至資料連讀都沒讀 Orz
有錯請指教,謝謝。
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 220.132.160.117
> -------------------------------------------------------------------------- <
作者: LPH66 ((short)(-15074)) 看板: java
標題: Re: [問題] ObjectOutputStream + ImageIO 出現的問題
時間: Fri Jan 1 21:59:43 2010
稍微查了一下 png 的 spec
我分析一下這段 binary 說一下原因
※ 引述《tkcn (小安)》之銘言:
: 這張圖片的 16 進位 data 如下: (69-byte)
: 89 50 4E 47 0D 0A 1A 0A
這是 png 的簽名
: 00 00 00 0D 49 48 44 52
: 00 00 00 01 00 00 00 01 08 02 00 00 00 90 77 53
: DE
這裡是第一塊 IHDR 區 00 00 00 0D 是長度 = 13
49 48 44 52 就是 "IHDR"
中間 13 byte 略
最後的 90 77 53 DE 是本區的 checksum
: 00 00 00 0C 49 44 41 54 78 DA 63 F8 FF FF 3F
: 00 05 FE 02 FE 33 12 95 14
這是第二塊 IDAT 區 同樣 00 00 00 0C 長度 = 12
49 44 41 54 "IDAT"
中間 12 byte 略
33 12 95 14 本區 checksum
: 00 00 00 00 49 45 4E
: 44 AE 42 60 82
最後這 12 byte 邏輯上也是一區 (叫 IEND 區)
同樣可以分解 00 00 00 00 長度 = 0
49 45 4E 44 "IEND"
AE 42 60 82 本區 checksum, 但因為都是這些資料所以也是固定的
因此實際上可以視為 png 的固定結尾
即所有 png 皆由這 12 byte 結束
: ImageIO.write() 會寫出 69 byte,
: 而 ImageIO.read() 只會讀入 53 byte,
: 我試著把原始的 png 檔案只留下前 53 byte,(或著是隨意竄改最後 16-byte)
: 這個時候秀圖軟體是沒辦法開啟這個檔案的,
: 但是用 ImageIO.read() 還是可以正常的讀入圖片。
: 我猜測最後的 16-byte 應該是 png 格式的 check sum 之類的東西吧,
: 而 ImageIO.read() 並沒有做這樣的驗證,甚至資料連讀都沒讀 Orz
: 有錯請指教,謝謝。
這樣一來這 16 byte 的正體就明白了:
它正是 IDAT 區的結尾 checksum 以及固定的 IEND 區
IEND 區不讀可以理解啦
但 spec 有說 IDAT 區可以有一個以上
這樣略過它的 checksum 不會出事嗎...?
--
'You've sort of made up for it tonight,' said Harry. 'Getting the
sword. Finishing the Horcrux. Saving my life.'
'That makes me sound a lot cooler then I was,' Ron mumbled.
'Stuff like that always sounds cooler then it really was,' said
Harry. 'I've been trying to tell you that for years.'
-- Harry Potter and the Deathly Hollows, P.308
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 140.112.28.92
推 tkcn:也許 IHDR 裡頭會紀錄有幾個 IDAT? 01/01 22:23
推 snowlike:試過RW後多個IDAT會被處理成一個,且忽略ancillary chunk 01/01 22:39
> -------------------------------------------------------------------------- <
作者: tkcn (小安) 站內: java
標題: Re: [問題] ObjectOutputStream + ImageIO 出現的問題
時間: Sat Jan 2 10:20:46 2010
※ 引述《Maisky (McDummy)》之銘言:
: 如果要用ObjectInputStream和ObjectOutputStream, 傳的object就應該要
: 是Serializable. 而BufferedImage不是Serializable,所以
: ImageIO.write(BufferedImage, "png", ObjectOutputStream)
並沒有說用 ObjectOutputStream 傳的 object 就必須 Serializable。
正確的說法應該是,
透過 writeObject() 傳出去的 object 才需要 Serializable。
ObjectOutputStream 用的是 Composition 的概念,
在他的內部其實是另一個 OutputStream,
當呼叫的 method 與 Object 的操作無關,
ObjectOutputStream 就會直接把呼叫轉給其內部的 OutputStream。
以 write(byte[]) 為例,source code 如下:
public void write(byte[] buf) throws IOException {
bout.write(buf, 0, buf.length, false);
}
// 其中 bout 就是 ObjectOutputStream 內部的 Stream
ImageIO 跟 ObjectOutputStream 的 source 都有點複雜,
所以接下來我就直接推理了。
如果你有看完這串所有的討論,
你應該會知道後來我們已經發現這個問題跟 ObjectOutputStream 無關,
即使是用 FileOutputStream 或 SocketOutputStream 也會有相同的問題,
並且我們已經找出問題並解決了。
如果如你所說,ImageIO 是用 Serializable 方式傳送圖片,
你要如何解釋為什麼使用 FileOutputStream 沒有問題?
---
ImageIO 不是用 Serializable 在傳資料的,
他是把 image,依照你所指定的 format (jpg, png, etc) 寫成對應的 Image File,
所以用 ImageIO 把 image 寫成檔案後,你家的繪圖軟體才打得開。
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 220.132.160.117
→ TonyQ:Composition? 這不是 Decoration 嗎 ?XD 01/02 10:32
→ tkcn:Decorated 的精神之一就是用 Composition 代替 Inheritance 01/02 10:57
→ TonyQ:你在開玩笑吧 , 你翻翻API就看的到 ObjectOutputStrema有 01/02 11:07
→ TonyQ:繼承 OutputStream , 且被裝飾者本就跟裝飾者要同一繼承體系 01/02 11:09
→ TonyQ:怎麼會有「代替」繼承這回事出現 , 這兩者在這的性質不同啊. 01/02 11:11
→ TonyQ:還是你在講得是子類別延伸出去的繼承樹 XD 01/02 11:16
→ TonyQ: *避免 01/02 11:18
→ tkcn:他還是有利用到繼承沒錯,但那是為了達成 setComponent的手段 01/02 11:25
→ TonyQ:如果是要 setComponent , interface-implement 就ok了 XD 01/02 11:29
→ tkcn:但是 OutputStream 裡頭有提供實作,我覺得這樣做並沒有問題 01/02 11:31
→ TonyQ:覺得應是OutputStream要拿來作為被裝飾的基底類別吧:3 01/02 11:31
→ TonyQ:覺得雖然Composition可以解釋 , 不過總覺得很彆扭 XD 01/02 11:32
→ tkcn:ttp://tinyurl.com/yfh8269 最後一段 01/02 11:33
→ TonyQ:是說講Composition 我總會想到 Composition Pattern . XDDD 01/02 11:37
> -------------------------------------------------------------------------- <
作者: sbrhsieh (偶爾想擺爛一下) 看板: java
標題: Re: [問題] ObjectOutputStream + ImageIO 出現的問題
時間: Sat Jan 2 14:57:33 2010
※ 引述《tkcn (小安)》之銘言:
: 終於把這個問題弄清楚了。
: 讓我從實驗的步驟開始說明吧。
: ---
: 1. 為了要方便觀察接收到的封包,
: 所以我做了一張只有 1x1 的圖片,
: 接著再用程式把這張圖片讀進去再重新寫成圖片。
: 這張圖片的 16 進位 data 如下: (69-byte)
: 89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52
: 00 00 00 01 00 00 00 01 08 02 00 00 00 90 77 53
: DE 00 00 00 0C 49 44 41 54 78 DA 63 F8 FF FF 3F
: 00 05 FE 02 FE 33 12 95 14 00 00 00 00 49 45 4E
: 44 AE 42 60 82
: 2. 從傳送端用 ImageIO.write() 送出這張圖片,
: 接收端會收到完全相同的 data。
: 3. 傳送端連續傳送兩次此圖片時,
: 也只是相同的 data 重複兩次,
: 並沒有看到我所預期的...多出來的 16-byte。
: 於是我又讓接收端用 ImageIO.read(),
: 結果還是一樣,第 1 張沒問題,第 2 張就會失敗。
: 4. 實驗做到這裡其實就可以看出端倪了,
: 剩下來都只是些驗證了,我就直接講結論吧。
: ImageIO.write() 會寫出 69 byte,
: 而 ImageIO.read() 只會讀入 53 byte,
: 我試著把原始的 png 檔案只留下前 53 byte,(或著是隨意竄改最後 16-byte)
: 這個時候秀圖軟體是沒辦法開啟這個檔案的,
: 但是用 ImageIO.read() 還是可以正常的讀入圖片。
: 我猜測最後的 16-byte 應該是 png 格式的 check sum 之類的東西吧,
: 而 ImageIO.read() 並沒有做這樣的驗證,甚至資料連讀都沒讀 Orz
: 有錯請指教,謝謝。
問題的癥結的確是因為 png plugin 提供的 ImageReader 沒有完整 consume png
數據所導致,造成傳送端與接收端在處理數據的順序/數量上的不匹配。
針對你之前的文章內容補充一些東西:
※ 引述《tkcn (小安)》之銘言:
: =================================================================
: 我剛剛在測試的時候也有試過利用 ByteArrayOutputStream,
: 不過沒有額外包成一個 class,結果還是失敗。
: 也有試過直接把 png file 的 data 丟進 ObjectOutputStream,
: ( png file 當初也是用 ImageIO.write() 寫入的,並且我也在接收方檢查過 header )
: 結果接收方還是沒辦法用 ImageIO.read() 接下來。
O 版工提供給你的版本改善了不匹配的錯誤,其重點不在於有無包裝成
Serializable 來傳輸,假如你改用下列這幾個 method 在你的程式裡,也可以正確
運作(傳輸/接收的程序上與 O 版工的做法相同)。
public static void serializeImage(RenderedImage image, String format,
DataOutput out) throws IOException {
ByteArrayOutputStream deposite =
new ByteArrayOutputStream(1024*1024);
ImageIO.write(image, format, deposite);
deposite.close();
byte[] data = deposite.toByteArray();
System.out.printf("[serializeImage] Image => %d bytes.%n",
data.length);
out.writeInt(data.length);
out.write(data);
}
public static BufferedImage deserializeImage(DataInput in)
throws IOException {
int frameSize = in.readInt();
System.out.printf("[deserializeImage] frame size: %d bytes.%n",
frameSize);
byte[] data = new byte[frameSize];
in.readFully(data);
return ImageIO.read(new ByteArrayInputStream(data));
}
// 假如圖檔格式己經是你要的就不需要先 decode 成 BufferedImage
public static void deserializeImageAndSave(DataInput in, File dest)
throws IOException {
int frameSize = in.readInt();
System.out.printf("[deserializeImage] frame size: %d bytes.%n",
frameSize);
byte[] data = new byte[frameSize];
in.readFully(data);
FileOutputStream out = new FileOutputStream(dest);
out.write(data);
out.close();
}
重點在傳送/接收兩方在處理數據的順序/數量上要匹配。ObjectOutputStream 會
在 wrapped stream 加上 header,而 ObjectInputStream 建構時會消耗掉這個
header 部份,所以把 socket stream 包裝成 ObjectIOStream 不會導致不匹配
得情況。有無包裝成 ObjectIOStream 來使用在這個 case 裡不是重點之一。
另外,不同的圖檔格式是由不同的 plugin 提供不同的 ImageReader/Writer,其
行為不盡相同。假如你原來的程式改成使用 jpg 格式,一樣會發生接收端在讀取
第二張圖片時無法正確 decode 出 image,但是其原因與使用 png 格式時不同。
jpg plugin 提供的 ImageReader 在處理數據時有"過度消耗"的行為。(png
ImageReader 是短缺消耗)
如果 jpg ImageReader 的 input(a ImageInputStream)只含有一個 image data,
其處理完第一個(也是最後一個) image 部份會因為遇到 EOF 而沒有消耗過多的
數據。(所以上面的 deserializeImage method 沒有受影響,會正確運作)
如果其 input 包含超過一個 image 的數據,ImageReader 在 decode 出第一個
image 時其已消耗的數據量已大於一個 image data。
而 ImageIO.read(...) methods 每次被調用時都是建立一個新的 ImageReader,
然後把指定的 ImageInputStream attach 到 ImageReader 來處理。這就類似你把
一個 InputStream wrap 進多個 BufferedInputStream 裡,然後依序從多個
BufferedInputStream 各讀取一部份的數據出來,你所得到的數據串接起來不會
等於原先 InputStream 所含的數據,因為每個 BufferedInputStream 一旦執行
過某個消耗 input 的操作後,其從 input 所消耗掉的數據量已超過可完成該操作
所需消耗的數據量。
那麼當你的程式接收端讀取了第一個 image 之後,實際上 socket stream 裡屬於
第二個 image 的數據有一部份被消耗掉了,所以第二次調用 ImageIO.read(...)
method 時,再一次建立的 ImageReader 會判讀成 input 內的數據不構成一個
image。(這個 ImageReader 讀到的數據是從一個 image data 的中間段開始)
對於 jpg ImageReader 的應用,可以讓接收端在接收數據前先建構一個
ImageReader,整個接收程序只使用此單一 ImageReader object 來 decode 出
多個 image。至於 png ImageReader 的短缺消耗行為則無法以相同的做法來改善。
我個人覺得 JRE 內建的 png ImageReader 的此等行為應該都算是 bug 了。
ImageIO.read(InputStream) 是設計來讓 client code 方便地 decode image
data,不需要處理 ImageReader 與各種參數上的設定。
它的確是可以從 stream 裡 decode 出 image,但是它沒有在 doc 裡明白
點出此操作會搞亂 stream 的狀態。
ImageIO.read(InputStream) doc 裡有強調 specified input stream 在此操作後
不會被 close(這是否暗示此 stream 接下來可用以讀取其他的數據?),但是目前
png ImageReader 的過少消耗行為等於讓 input stream 留在一個不可預期的狀態
而無法再被使用。
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 218.173.128.149
※ 編輯: sbrhsieh 來自: 218.173.128.149 (01/02 15:00)
推 godfat:這樣設計有什麼好處..? 好怪? 01/02 18:27