2009年03月30日

libjpegでメモリー上のjpegファイルを読み込む

delphiのTjpegよりは内部を触れる感じのAPIを使えると思ったのだが
大間違いで妙にメンバーが多い割に基本的には提供された大雑把な関数しか使えない。
Tjpegはむしろよくまとめていたということになる。
最たるものは入出力でメモリーから呼び出すためにはソースのjpeg_stdio_destをベースに
メモリー用の関数を作って登録と面倒な手間をかけないといけない様だ。
但しこれはUNIX限定の話であってWindowsの場合はjpeg_stdio_destのままでかまわない。
なぜならWindowsの場合はFILEというのはリソースストリームの「handle」の種類に過ぎず
OSの手続きで作ったメモリーストリームハンドルが使えるからだ。

そしていまだに「windowsではijgを使うしかない」と勘違いしている人もいる様だが
Windowsの場合jpeg読み込みはOleLoadPictureというolepro32由来の関数が使えるので
小規模プログラムの場合ijgライブラリーをあえて使う理由は無い。

そんな事を考えながらも今回はjdatasrc.cをjmemsrc.cに変更して
メモリーないのjpegデータからsdlに展開してみた。
ファイルサイズに制約の無いアプリならばijgをベースにするにしても改造して
自アプリ内部でのAPIにするか改造したライブラリーを使う方が良い。
UNIXの場合ためしていないがMagickWandが一応一通りのセットになっているのがまし
簡便に使うならばFILEで使う方がごちゃつかない。
大抵の場合ファイルからの読み込みですむはずだ。
効率がよいと判断したポイントで今回の手法を使うのがいいと思う。

まずjpeg_stdio_srcのソースを見てみる。jdatasrc.cだ。
ぐだぐだと書いてあるコメントを取り除くと


#include "jinclude.h"
#include "jpeglib.h"
#include "jerror.h"

typedef struct {
struct jpeg_source_mgr pub;

FILE * infile;
JOCTET * buffer;
boolean start_of_file;
} my_source_mgr;

typedef my_source_mgr * my_src_ptr;

#define INPUT_BUF_SIZE 4096

METHODDEF(void)
init_source (j_decompress_ptr cinfo)
{
my_src_ptr src = (my_src_ptr) cinfo->src;
src->start_of_file = TRUE;
}

METHODDEF(boolean)
fill_input_buffer (j_decompress_ptr cinfo)
{
my_src_ptr src = (my_src_ptr) cinfo->src;
size_t nbytes;

nbytes = JFREAD(src->infile, src->buffer, INPUT_BUF_SIZE);

if (nbytes <= 0) {
if (src->start_of_file)
ERREXIT(cinfo, JERR_INPUT_EMPTY);
WARNMS(cinfo, JWRN_JPEG_EOF);

src->buffer[0] = (JOCTET) 0xFF;
src->buffer[1] = (JOCTET) JPEG_EOI;
nbytes = 2;
}

src->pub.next_input_byte = src->buffer;
src->pub.bytes_in_buffer = nbytes;
src->start_of_file = FALSE;

return TRUE;
}


METHODDEF(void)
skip_input_data (j_decompress_ptr cinfo, long num_bytes)
{
my_src_ptr src = (my_src_ptr) cinfo->src;

if (num_bytes > 0) {
while (num_bytes > (long) src->pub.bytes_in_buffer) {
num_bytes -= (long) src->pub.bytes_in_buffer;
(void) fill_input_buffer(cinfo);

}
src->pub.next_input_byte += (size_t) num_bytes;
src->pub.bytes_in_buffer -= (size_t) num_bytes;
}
}


METHODDEF(void)
term_source (j_decompress_ptr cinfo)
{
}

GLOBAL(void)
jpeg_stdio_src (j_decompress_ptr cinfo, FILE * infile)
{
my_src_ptr src;

if (cinfo->src == NULL) {
cinfo->src = (struct jpeg_source_mgr *)
(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
SIZEOF(my_source_mgr));
src = (my_src_ptr) cinfo->src;
src->buffer = (JOCTET *)
(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
INPUT_BUF_SIZE * SIZEOF(JOCTET));
}

src = (my_src_ptr) cinfo->src;
src->pub.init_source = init_source;
src->pub.fill_input_buffer = fill_input_buffer;
src->pub.skip_input_data = skip_input_data;
src->pub.resync_to_restart = jpeg_resync_to_restart;
src->pub.term_source = term_source;
src->infile = infile;
src->pub.bytes_in_buffer = 0;
src->pub.next_input_byte = NULL;
}



となっている。ここでファイル依存の記述がされているのはmy_src_ptrで
関数で直接FILEにアクセスしているのは

  1. jpeg_stdio_src

  2. fill_input_buffer


の二つだけであるのでその辺だけ書き換えて新しい「jmemsrc.c」を作ればよさそうだ。
METHODDEFというのはstatic typeの模様
とりあえず「my_source」を「src_memory」に
「stdio」「input」を「memory」に
「my_src_ptr」を「src_mem_ptr」に
置き換えてみる。バッファは固定長で一発変換、ネットなどのストリームは考えないことにした。

dummy_memmoryという空の関数を作り何もしない関数の代表として使う。
my_source_mgr ->src_memory_mgr
my_src_ptr ->src_mem_ptr
skip_input_data -> skip_memory_data
fill_input_buffer -> fill_memory_buffer
jpeg_stadio_src ->jpeg_memory_src

ヘッダーはライブラリーでないので書き換える。

#include <jinclude.h>
#include <jpeglib.h>
#include <jerror.h>


削りすぎ?ポインタとかlenとか入れたきゃ入れてもいいけれど使わない。

typedef struct {
struct jpeg_memory_mgr pub;
JOCTET * buffer;
} src_memory_mgr;

typedef src_memory_mgr * src_mem_ptr;


ダミー いらないかも

METHODDEF(void)
dummy_memory (j_decompress_ptr cinfo)
{
// src_mem_ptr src = (src_mem_ptr) cinfo->src;
}


呼ばれた時用のダミー

METHODDEF(boolean)
fill_memory_buffer (j_decompress_ptr cinfo)
{
src_mem_ptr src = (src_mem_ptr) cinfo->src;
src->buffer[0] = (JOCTET) 0xFF;
src->buffer[1] = (JOCTET) JPEG_EOI;

src->pub.next_input_byte = src->buffer;
src->pub.bytes_in_buffer = 2;
return TRUE;
}


メモリー直接なのでループは削除

METHODDEF(void)
skip_memory_data (j_decompress_ptr cinfo, long num_bytes)
{
src_mem_ptr src = (src_mem_ptr) cinfo->src;
src->pub.next_input_byte += (size_t) num_bytes;
src->pub.bytes_in_buffer -= (size_t) num_bytes;
}


jpeg_memory_src 本体


GLOBAL(void)
jpeg_memory_src (j_decompress_ptr cinfo, void* data, unsigned long len)
{
//var
src_mem_ptr src;
//begen
if (cinfo->src == NULL) {
cinfo->src = (struct jpeg_source_mgr *)
(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
SIZEOF(src_memory_mgr));
src = (src_mem_ptr) cinfo->src;
src->buffer = (JOCTET *)
(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
len * SIZEOF(JOCTET));
}

src = (src_mem_ptr) cinfo->src;
src->pub.init_source = dummy_memory;//change
src->pub.fill_input_buffer = fill_memory_buffer;//change
src->pub.skip_input_data = skip_memory_data;//change
src->pub.resync_to_restart = jpeg_resync_to_restart;
src->pub.term_source = dummy_memory;//change

src->pub.bytes_in_buffer = len; //change
src->pub.next_input_byte = (JOCTET*)data; //change
}


ふが、
これを

gcc -Wall -pipe -O2 -march=pentium3 -I/usr/local/include \
-c jmemsrc.c

で jmemsrc.oを作っておく、基本的にこれをリンクすればjpeg_memory_srcが使える。

変数名はいつもどおり
メモリーに読み込む、効果は分からないが8byte単位にした。

filename=IMGNAME;
if ((fp = fopen(filename, "rb")) == NULL) {
printf(stderr, "can't open %s\n", filename);
exit(1);
}
fsize=fseek(fp,0,SEEK_END);
fsize=ftell(fp);
rewind(fp);
jmem=malloc(fsize+128);
fsize2=fsize >> 3;
fread(jmem,8,fsize2,fp);
fsize2=fsize2 <<3;
bits=jmem+fsize2;
fread((jmem+fsize2),1,fsize-fsize2,fp);


SDL初期化

if ( SDL_Init(SDL_INIT_VIDEO) < 0 ) {

exit(1);

}

sdl_sf = SDL_SetVideoMode(
SCREEN_WIDTH,SCREEN_HEIGHT,
SCREEN_BPP,SDL_SWSURFACE
);


jpeg_memory_srcをソースに

cinfo.err = jpeg_std_error(&jerr);
jpeg_create_decompress(&cinfo);

jpeg_memory_src(&cinfo,jmem,fsize);


こっからはファイルと同じ、表示にSDLを使うのは手っ取り早いから今回も工夫なし
次回からは「draw」にしようと思う。

jpeg_read_header(&cinfo, TRUE);
jpeg_start_decompress(&cinfo);
W = cinfo.output_width;
H = cinfo.output_height;

img1=SDL_CreateRGBSurface(SDL_SWSURFACE, W, H, 32, rmask, gmask, bmask,amask);//RGBA
bits=img1->pixels;
k=W<<2;
for(i=0; i < H; i++)
{
jpeg_read_scanlines(&cinfo, &bits, 1);
for(j=W-1; j >=0; j--)
{
pix=*(int*)(bits+3*j)|amask;
*(int*)(bits+4*j)=pix;
}
bits+=k;
}

//jpeg_abort_decompress(&cinfo);
jpeg_finish_decompress(&cinfo);
puts("memocopyend2");

jpeg_destroy_decompress(&cinfo);

dst.x = 300;dst.y = 200;
dst.w = 160;dst.h = 120;

SDL_BlitSurface( img1, &src, sdl_sf, &dst );
SDL_Flip( sdl_sf );


ループとか

SDL_WM_SetCaption( WINDOW_CAPTION, NULL );
done = SDL_FALSE;
while (!done)
{
SDL_WaitEvent(&ev);
switch (ev.type)
{
case SDL_QUIT :
done = SDL_TRUE;
break;
case SDL_KEYDOWN :
if (ev.key.keysym.sym == SDLK_q ||
ev.key.keysym.sym == SDLK_ESCAPE )
{
done = SDL_TRUE;
}
break;
default:
break;
}
}
releace://エラー用goto ポイント
SDL_FreeSurface( img1 );
//

SDL_Quit();

free(jmem);//忘れずに開放
return(0);

}


UNIXならほんとはpipeでforkかスレッドかコマンドラインで
stdio_srcでやりとりしていいんじゃないかと思う。
実際
djpegをパイプでcjpegにつなげるのだしソケットの類もそうだ。
メモリー上での使用目的はアーカイブからの画像抽出とかテクスチャーの再利用
あるいはexifなどが目的になる。

ほんとは素直にsdl_imageを使うのが正しくてこういう路線に走っちゃうのは
間違ったプログラミングではないかと感じる。
しかしどうせやるならハフマン展開したあとのメモリー取得とかも目指したい。


ラベル:libjpeg SDL 画像
posted by Xo_ox at 23:44| Comment(0) | クロスプラットフォーム | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
×

この広告は180日以上新しい記事の投稿がないブログに表示されております。