読者です 読者をやめる 読者になる 読者になる

プログラムdeタマゴ

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

読み込み中の画像を表示させる

↑というのがググっても出てこなかったのでやってみた。



Toolkit.createImageApplet.getImageなどで画像を読み込むと非同期で読み込んでくれる。
この読み込み状況はGraphics.drawImageImage.getWidthなどを呼び出して、ImageObserverを登録することで逐一受け取ることが出来る。


そこで、普通に考えればこのImageObserverに渡される値から読み込み度合いを判断して、上から順に表示していけるんじゃない?とか今まで思っていたわけだ。


実際にその考えを直に反映したソースは以下の様になるだろう。(importはカット)

public class TestImageView extends JComponent{
	private Image view = null;
	private int vieww=10,viewh=10;

	public TestImageView(URL imageURL){
		view = Toolkit.getDefaultToolkit().createImage(imageURL);
	}

	@Override
	protected void paintComponent(Graphics g) {
		g.drawImage(view, 0, 0, vieww, viewh, this);
	}

	@Override
	public Dimension getPreferredSize() {
		return new Dimension(vieww,viewh);
	}

	@Override
	public boolean imageUpdate(Image img, int infoflags, int x, int y, int w,int h) {
		if((infoflags&WIDTH)!=0 || (infoflags&HEIGHT)!=0)
			//今まで分かってなかった画像の高さ、幅がわかった
		{
			vieww = w;
			viewh = h;
		}
		else if((infoflags & SOMEBITS)!=0)
			//新たな行が読み込み完了した
		{
			repaint(x,y,w,h);
		}
		else if((infoflags& ALLBITS)!=0)
			//全部読み込み完了
		{
			repaint();
		}
		else if((infoflags & ERROR)!=0)
			//何か読み込み中にエラーがあった
		{}
		return true;
	}
}




まず、コンストラクターで受け取ったURLの画像を読み込む。

view = Toolkit.getDefaultToolkit().createImage(imageURL);




次にpaintComponentで画像を描画しようとすることでImageObserverである自身を登録する。ちなみに、2度以上同じImageObserverを登録しても2度呼ばれるとかいうことはない。

g.drawImage(view, 0, 0, vieww, viewh, this);

そして、ImageObserverの関数imageUpdateで新たな行が読み込まれる度にrepaintを発行する。

	@Override
	public boolean imageUpdate(Image img, int infoflags, int x, int y, int w,int h) {
		if((infoflags&WIDTH)!=0 || (infoflags&HEIGHT)!=0)
			//今まで分かってなかった画像の高さ、幅がわかった
		{
			vieww = w;
			viewh = h;
		}
		else if((infoflags & SOMEBITS)!=0)
			//新たな行が読み込み完了した
		{
			repaint(x,y,w,h);
		}
		else if((infoflags& ALLBITS)!=0)
			//全部読み込み完了
		{
			repaint();
		}
		return true;
	}



完璧だ。完璧である。

しかし、結果は残念なことに

1まっさらな状態が表示される



↓数秒後


2.全体が一気に表示される*1




という意図した物とは違う挙動をする。

drawImageではどうあっても書きかけの画像を描画したくないらしい。


そこで、強制的に作りかけの画像を取り出してみた。


Toolkitで作成した画像はsun.awt.image.ToolkitImageというクラスである。このToolkitImageクラスにはgetBufferedImageという関数がある。これが実はその作りかけの画像の実態だ。

そこで、imageUpdate関数を以下の様に書き換える

	@Override
	public boolean imageUpdate(Image img, int infoflags, int x, int y, int w,int h) {
		if((infoflags&WIDTH)!=0 || (infoflags&HEIGHT)!=0)
			//今まで分かってなかった画像の高さ、幅がわかった
		{
			vieww = w;
			viewh = h;
		}
		else if((infoflags & SOMEBITS)!=0)
			//新たな行が読み込み完了した
		{
			if(img instanceof sun.awt.image.ToolkitImage){
				view = ((sun.awt.image.ToolkitImage)img).getBufferedImage();
			}
			repaint(x,y,w,h);
		}
		else if((infoflags& ALLBITS)!=0)
			//全部読み込み完了
		{
			view = img;
			repaint();
		}
		else if((infoflags & ERROR)!=0)
			//何か読み込み中にエラーがあった
		{

		}
		return true;
	}


表示するviewを内部のBufferedImageに変えてしまう。
読み込みが完了したら元のimgに戻しておく。
これによって読み込み中でもdrawImageで描画可能なImageを取得できた。
実行すると以下の様になる








きちんと読み込みに応じて画像が描画されている。



しかし、見て分かるように背景が黒くなってしまっている。
これでは何かと不都合な場面もあるだろう。
というわけで、読み込み中は画像を別な画像に転写するといいだろう。

最終的なソースコードは以下の様になった。




なお、どうもImageToolkitはpngを順次解凍していくのは苦手らしく、うまくいかない。
JPEGとGIFでは上から表示されるのを確認した。

import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.Window;
import java.net.MalformedURLException;
import java.net.URL;

import javax.swing.JComponent;
import javax.swing.JFrame;
public class TestImageView extends JComponent{
	private Image view = null;
	private int vieww=10,viewh=10;

	public TestImageView(URL imageURL){
		view = Toolkit.getDefaultToolkit().createImage(imageURL);
		view.getWidth(this);//ImageObserverを登録するため
	}

	@Override
	protected void paintComponent(Graphics g) {
		g.drawImage(view,0,0, this);
	}

	@Override
	public Dimension getPreferredSize() {
		return new Dimension(vieww,viewh);
	}

	@Override
	public boolean imageUpdate(Image img, int infoflags, int x, int y, int w,int h) {
		if((infoflags&WIDTH)!=0 || (infoflags&HEIGHT)!=0)
			//今まで分かってなかった画像の高さ、幅がわかった
		{
			vieww = w;
			viewh = h;

			//実際に描画するイメージの作成
			view = createImage(w, h);
			Graphics g = view.getGraphics();
			g.setColor(getBackground());
			g.fillRect(0, 0, w, h);
			g.dispose();

			//今回はウィンドウサイズを変更します
			Container c=getParent();
			while(c!=null && !(c instanceof Window)){
				c = c.getParent();
			}
			if(c!=null){
				((Window)c).pack();
			}
		}
		else if((infoflags & SOMEBITS)!=0)
			//新たな行が読み込み完了した
		{
			if(view == img){
				int wi = img.getWidth(null),he = img.getHeight(null);
				view = createImage(wi, he);
				Graphics g = view.getGraphics();
				g.setColor(getBackground());
				g.fillRect(0, 0, wi, he);
				g.dispose();
			}
			if(img instanceof sun.awt.image.ToolkitImage){
				Graphics g = view.getGraphics();
				g.setClip(x, y, w, h);
				g.drawImage(((sun.awt.image.ToolkitImage)img).getBufferedImage(), 0, 0, null);
				g.dispose();
			}
			repaint(x,y,w,h);
		}
		else if((infoflags& ALLBITS)!=0)
			//全部読み込み完了
		{
			if(view != null)view.flush();
			view = img;
			repaint();
		}
		else if((infoflags & ERROR)!=0)
			//何か読み込み中にエラーがあった
		{
			System.out.println("load error");
		}else if((infoflags & FRAMEBITS)!=0){
			//アニメーション
			view =img;
			repaint();
		}
		return true;
	}


	public static void main(String[] args) throws MalformedURLException {
		URL url = new URL(args[0]);
		JFrame frame = new JFrame("読み込みイメージ表示テスト");
		TestImageView c = new TestImageView(url);
		frame.add(c);
		frame.setDefaultCloseOperation(3);
		frame.pack();
		frame.setVisible(true);
	}

}







*1:画像は適当な転送速度が遅いサイトから落としてきているためモザイクをかけてあります。