/* =============================================================================== JpegViewer.mc : JPEG 画像ファイルをウィンドウに表示する =============================================================================== Copyright (C) 2013 Masahiko Watanabe Edition History: 2013.08.02 作成開始 -----------------------------------------------------------------------------*/ /*****/ // GK Library のモジュールをロードする場合 #include #include if( ::Module.Include( "GkLibrary" ) > 0 ) Main( argc, argv ); return; /*****/ /***** // GK Library のソースをインクルードする場合 #include Main( argc, argv ); return; /*****/ //----------------------------------------------------------------------------- // デバッグプリント無効化(リリース時) #set pc #comment #set pv #set px #comment // デバッグプリント有効化(デバッグ時) //#set pc print //#set pv print //#set px #set p print //----------------------------------------------------------------------------- #set VSB_Xd 16 // 垂直スクロールバー幅 #set HSB_Yd 16 // 水平スクロールバー高 #set MAX_WND_Xd 800 #set MAX_WND_Yd 600 #set MIN_WND_Xd 200 #set MIN_WND_Yd 150 #set SML_WND_Xd 400 #set SML_WND_Yd 300 #set CharYd 16 // フォントの文字高 #set RGB_BACK #RGB(0,0,0) #set RGB_TEXT #RGB(255,255,255) #set WM_APP_SHOW_JPEG_FILE (#WM_APP+101) #set WM_APP_JPEG_DECODE_END (#WM_APP+102) #set TMR_UPDATE_VIEW 301 // メニュー項目番号 #set CMD_OPEN 1001 #set CMD_SAVE 1002 #set CMD_END 1003 //----------------------------------------------------------------------------- function Main( argc, argv ) { #pc "開始"; ^DefValue = null; class ^JpegDecoder : ::Buffer {} class ^JpegViewWindow : ::GK.Window {} ^BackBrush = ::GK.Brush( #RGB_BACK ); ^TextFont = ::GK.Font( "MS Pゴシック", #CharYd ); #pv wnd = ^JpegViewWindow( argv[0] ); wnd.Open(); if( wnd.Jpeg ) { ::GK.SetParaProc( #TRUE ); #pv wnd.JpegProcThread'start; } else if( wnd.ErrMsg ) wnd.ShowErrorMsg(); ::GK.WindowMsgLoop(); //delete ^TextFont, ^BackBrush; #pc "終了"; } //----------------------------------------------------------------------------- function ^JpegViewWindow.Construct( jpeg_file ) { #pc "Construct", jpeg_file; #pv .FilePathToOpen = jpeg_file; .SetupJpeg(); .[ ::GK.Window.Construct ]( .JpegViewWndTitle(), , , .Wnd_Xd, .Wnd_Yd, , #WS_EX_CLIENTEDGE ); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^JpegViewWindow.Destruct() { #pc "Destruct", .hWnd; //.[ ::GK.Window.Destruct ](); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^JpegViewWindow.OnClose() { #pc "OnClose", .hWnd; .CloseReq = #TRUE; if( .JpegThread? ) { .Jpeg.StopReq = #TRUE; return -1; } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^JpegViewWindow.OnCreate() { #pc "OnCreate"; #pv .HScrBar = .AddScrollBar( "H", ::GK.Rect( 0, 0, 0, 0 ) ); .HScrBar.OnScroll = function( act, pos ) { .owner.OnHScroll( act ); }; #pv .VScrBar = .AddScrollBar( "V", ::GK.Rect( 0, 0, 0, 0 ) ); .VScrBar.OnScroll = function( act, pos ) { .owner.OnVScroll( act ); }; .SetupMainMenu(); //(注)この実行で、.OnSize() がコールされる .gc.SetFont( ^TextFont ); .gc.SetTextColor( #RGB_TEXT ); .gc.SetBackColor( #RGB_BACK ); .gc.SetBackMode( #TRANSPARENT ); ::GK.DragAcceptFiles( .hWnd, #TRUE ); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^JpegViewWindow.OnSize() { #pc "OnSize", .gc.Xd, .gc.Yd; #pv .HScrBar.Show? = ( .Bmp && .Bmp.Xd > .gc.Xd ); #pv .VScrBar.Show? = ( .Bmp && .Bmp.Yd > .gc.Yd ); .ViewXd = .gc.Xd - ( .VScrBar.Show? ? #VSB_Xd : 0 ); if( .ViewXd < 0 ) .ViewXd = 0; if( .ViewXo < 0 || ( .Bmp && .ViewXo + .ViewXd > .Bmp.Xd )) .ViewXo = 0; #pc .ViewXo, .ViewXd; .ViewYd = .gc.Yd - ( .HScrBar.Show? ? #HSB_Yd : 0 ); if( .ViewYd < 0 ) .ViewYd = 0; if( .ViewYo < 0 || ( .Bmp && .ViewYo + .ViewYd > .Bmp.Yd )) .ViewYo = 0; #pc .ViewYo, .ViewYd; if( .HScrBar.Show? ) { .HScrBar.Move( ::GK.Rect( 0, .gc.Yd - #HSB_Yd, .ViewXd, #HSB_Yd ) ); .HScrBar.SetRange( 0, .Bmp.Xd ); .HScrBar.SetPageSize( .ViewXd ); .HScrBar.SetPos( .ViewXo ); } else .HScrBar.Move( ::GK.Rect( 0, 0, 0, 0 ) ); if( .VScrBar.Show? ) { .VScrBar.Move( ::GK.Rect( .gc.Xd - #VSB_Xd, 0, #VSB_Xd, .ViewYd ) ); .VScrBar.SetRange( 0, .Bmp.Yd ); .VScrBar.SetPageSize( .ViewYd ); .VScrBar.SetPos( .ViewYo ); } else .VScrBar.Move( ::GK.Rect( 0, 0, 0, 0 ) ); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^JpegViewWindow.OnPaint() { //#pc "OnPaint"; Rc = ::GK.Rect(); .GetHotRect( Rc ); if( ! .Bmp ) { .gc.ClearRect( Rc, #RGB_BACK ); .gc.DrawText( ::GK.Rect( 0, .gc.Yd / 2 - #CharYd, .gc.Xd, 0 ), "JPEG ファイルを、ここにドロップすると\n" "その画像を表示します!", #DT_CENTER | #DT_NOCLIP ); return; } Rv = ::GK.Rect( 0, 0, .ViewXd, .ViewYd ); Rv.And( Rc ); Ri = ::GK.Rect( 0, 0, .Bmp.Xd, .Bmp.Yd ); dst = Ri; dst.Shift( , , -.ViewXo, -.ViewYo ); if( ! dst.And( Rv ) ) goto CLR_ALL; src = ::GK.Rect( Rv.Xo + .ViewXo, Rv.Yo + .ViewYo, Rv.Xd, Rv.Yd ); if( ! src.And( Ri ) ) goto CLR_ALL; .gc.DrawBitmap( .Bmp, dst, src ); CLR_REST: R1 = ::GK.Region( Rc ); R2 = ::GK.Region( dst ); switch( R1 -= R2 ) { case #COMPLEXREGION: //#pc "COMPLEXREGION"; case #SIMPLEREGION: .gc.FillRegion( R1, ^BackBrush ); } return; CLR_ALL: .gc.ClearRect( Rc, #RGB_BACK ); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^JpegViewWindow.SetupMainMenu() { #pv .menu_file = ::GK.Menu(); .menu_file.InsertItem( #CMD_OPEN, "開く" ); // .menu_file.InsertItem( #CMD_SAVE, "保存" ); // .menu_file.InsertSeparator(); .menu_file.InsertItem( #CMD_END , "終了" ); #pv .top_menu = ::GK.TopMenu(); .top_menu.InsertMenu( .menu_file, "ファイル" ); .SetMenuBar( .top_menu ); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^JpegViewWindow.OnMenu( id ) { #pc "OnMenu: ": id; //.Update(); switch( id ) { case #CMD_OPEN: .DlgReadJpegFile(); break; //case #CMD_SAVE: .DlgSaveJpegFile(); break; case #CMD_END: .Close(); break; return; } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^JpegViewWindow.OnHScroll( act ) { switch( act ) { case #SB_TOP: Xo = 0; break; case #SB_LINEUP: Xo = .ViewXo - .ViewXd / 16; break; case #SB_PAGEUP: Xo = .ViewXo - .ViewXd; break; case #SB_THUMBPOSITION: case #SB_THUMBTRACK: Xo = .HScrBar.GetSlidePos(); break; case #SB_LINEDOWN: Xo = .ViewXo + .ViewXd / 16; break; case #SB_PAGEDOWN: Xo = .ViewXo + .ViewXd; break; case #SB_BOTTOM: Xo = 0xFFF`FFFF; break; case #SB_ENDSCROLL: default: return; } if( Xo > .Bmp.Xd - .ViewXd ) Xo = .Bmp.Xd - .ViewXd; if( Xo < 0 ) Xo = 0; if( Xo != .ViewXo ) { dx = .ViewXo - Xo; .ViewXo = Xo; .HScrBar.SetPos( Xo ); .Scroll( dx, 0, ::GK.Rect( 0, 0, .ViewXd, .ViewYd ) ); .Update(); } //#pc "HScroll", act, Xo; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^JpegViewWindow.OnVScroll( act ) { switch( act ) { case #SB_TOP: Yo = 0; break; case #SB_LINEUP: Yo = .ViewYo - .ViewYd / 16; break; case #SB_PAGEUP: Yo = .ViewYo - .ViewYd; break; case #SB_THUMBPOSITION: case #SB_THUMBTRACK: Yo = .VScrBar.GetSlidePos(); break; case #SB_LINEDOWN: Yo = .ViewYo + .ViewYd / 16; break; case #SB_PAGEDOWN: Yo = .ViewYo + .ViewYd; break; case #SB_BOTTOM: Yo = 0xFFF`FFFF; break; case #SB_ENDSCROLL: default: return; } if( Yo > .Bmp.Yd - .ViewYd ) Yo = .Bmp.Yd - .ViewYd; if( Yo < 0 ) Yo = 0; if( Yo != .ViewYo ) { dy = .ViewYo - Yo; .ViewYo = Yo; .VScrBar.SetPos( Yo ); .Scroll( 0, dy, ::GK.Rect( 0, 0, .ViewXd, .ViewYd ) ); .Update(); } //#pc "VScroll", act, Yo; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^JpegViewWindow.ShowErrorMsg() { .MessageBox( .ErrMsg, "JPEG Viewer", #MB_ICONERROR | #MB_OK ); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^JpegViewWindow.JpegViewWndTitle() { if( .FilePathToOpen && .Bmp ) { ::File.GetNamePart( .FilePathToOpen, , base_name'new!, ext_name'new! ); return base_name + ext_name + " -- JPEG Viewer"; } else return "JPEG Viewer"; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^JpegViewWindow.DlgReadJpegFile() { OFN.Title = "JPEG ファイル"; OFN.FileSpec'USHORT( #MAX_PATH + 4 ); //OFN.FileSpec'puts( @@@ ); OFN.InitialDir = ::Module.FilePath(); OFN.Filter'USHORT( 256 ); i = 0; i += OFN.Filter'puts( "JPEG ファイル(*.jpg)", i ) + 1; i += OFN.Filter'puts( "*.jpg", i ) + 1; i += OFN.Filter'puts( "全て(*.*)", i ) + 1; i += OFN.Filter'puts( "*.*", i ) + 1; OFN.Flags = #OFN_PATHMUSTEXIST | #OFN_FILEMUSTEXIST; if( ! .GetOpenFileName( OFN ) ) return; #pc "File Name = ": OFN.FileSpec'gets; .FilePathToOpen = OFN.FileSpec'gets; .PostMessage( #WM_APP_SHOW_JPEG_FILE, 0, 0 ); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^JpegViewWindow.OnTimer( id ) { switch( id ) { case #TMR_UPDATE_VIEW: if( .JpegThread? ) .InvalidateRect(); break; } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^JpegViewWindow.OnDropFiles( hDrop ) { #pc "OnDropFiles"; file_path'USHORT( #MAX_PATH + 4 ); ::GK.DragQueryFile( hDrop, 0, file_path, #MAX_PATH ); .FilePathToOpen = file_path'gets; .PostMessage( #WM_APP_SHOW_JPEG_FILE, 0, 0 ); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^JpegViewWindow.OnAppMessage( iMsg, wParam, lParam ) { #pc "OnAppMessage", iMsg; switch( iMsg ) { case #WM_APP_SHOW_JPEG_FILE: if( .JpegThread? ) { .ShowReq = #TRUE; .Jpeg.StopReq = #TRUE; break; } ::GK.SetForegroundWindow( .hWnd ); .SetupJpeg(); .SetTitle( .JpegViewWndTitle() ); .InvalidateRect(); .GetWindowRect( R'STRUCT ); R.Xd = .Wnd_Xd; R.Yd = .Wnd_Yd; .Move( R ); .Update(); #pc "#WM_APP_SHOW_JPEG_FILE: ": .Jpeg; if( .Jpeg ) { ::GK.SetParaProc( #TRUE ); #pv .JpegProcThread'start; } else if( .ErrMsg ) .ShowErrorMsg(); break; case #WM_APP_JPEG_DECODE_END: #pc "JPEG デコード -- 終了"; .StopTimer( #TMR_UPDATE_VIEW ); ::GK.SetParaProc( #FALSE ); .JpegThread? = #FALSE; if( .CloseReq ) .Close(); else if( .ShowReq ) { .ShowReq = #FALSE; .PostMessage( #WM_APP_SHOW_JPEG_FILE, 0, 0 ); } else { .InvalidateRect(); if( .Jpeg.ErrMsg ) .ShowErrorMsg(); } break; } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^JpegViewWindow.JpegProcThread() { #pc "JpegProcThread -- Start"; .JpegThread? = #TRUE; .CloseReq = #FALSE; .StartTimer( #TMR_UPDATE_VIEW, 50 ); delete .Jpeg.ErrMsg; .Jpeg.StopReq = #FALSE; if( .Jpeg.ProcMarker( .Bmp.Pixs, .Bmp.Xb ) != #OK ) .ErrMsg = .Jpeg.ErrMsg; .PostMessage( #WM_APP_JPEG_DECODE_END, 0, 0 ); #pc "JpegProcThread -- End"; } //----------------------------------------------------------------------------- function ^JpegViewWindow.SetupJpeg() { delete .Bmp, .Jpeg, .Wnd_Xd, .Wnd_Yd; .ViewXo = 0; .ViewYo = 0; if( ! .FilePathToOpen ) goto NO_IMAGE; file = ::File.Open( .FilePathToOpen, "in" ); if( file == null ) { #pc .ErrMsg = ::ErrorMessage( $err_code, $err_info ); goto NO_IMAGE; } #pv .Jpeg = ^JpegDecoder(); bn = file.Read( .Jpeg ); #pc file.name, bn; delete file; if( .Jpeg.ProcMarker() != #OK ) { .ErrMsg = .Jpeg.ErrMsg; delete .Jpeg; goto NO_IMAGE; } #pv .Bmp = ::GK.Bitmap(); .Bmp.Setup( .Jpeg.SOF.X, .Jpeg.SOF.Y, 24, null ); .Bmp.Pixs'UBYTE( .Bmp.Xb * .Bmp.Yd ); .Bmp.Bp = .Bmp.Pixs'addr; R'LONG(4); R(2) = .Bmp.Xd'cut( /*#MIN_WND_Xd*/, #MAX_WND_Xd ); // RECT.right R(3) = .Bmp.Yd'cut( /*#MIN_WND_Xd*/, #MAX_WND_Yd ); // RECT.bottom if( R(3) < .Bmp.Yd ) // 垂直スクロールバー要? R(2) += #VSB_Xd; if( R(2) < .Bmp.Xd ) // 水平スクロールバー要? R(3) += #HSB_Yd; if( ::GK.AdjustWindowRectEx( R, #WS_OVERLAPPEDWINDOW, #TRUE, #WS_EX_CLIENTEDGE )) { .Wnd_Xd = R(2) - R(0); // = .right - .left .Wnd_Yd = R(3) - R(1); // = .bottom - .top } END: #pc "SetupJpeg: ": .FilePathToOpen, .Wnd_Xd, .Wnd_Yd; #pc " ": .Bmp.Xd, .Bmp.Yd, .Bmp.Zd, .Bmp.Xb, .Bmp.Pixs, .Bmp.Bp; return; NO_IMAGE: .Wnd_Xd = #SML_WND_Xd; .Wnd_Yd = #SML_WND_Yd; goto END; } //============================================================================= // マーカー定義 #set SOF 0xFFC0 // 基本DCT方式のフレーム開始 #set DHT 0xFFC4 // ハフマンテーブル定義 #set RST_0 0xFFD0 // リスタートインターバル終了 #0 #set RST_7 0xFFD7 // リスタートインターバル終了 #7 #set SOI 0xFFD8 // 画像開始 #set EOI 0xFFD9 // 画像終了 #set SOS 0xFFDA // スキャン開始 #set DQT 0xFFDB // 量子化テーブル定義 #set DRI 0xFFDD // リスタートインターバル定義 #set APP_0 0xFFE0 // アプリケーションデータセグメント#0 #set APP_F 0xFFEF // アプリケーションデータセグメント#F #set COM 0xFFFE // コメント function ^JpegDecoder.Construct() { .[ ::Buffer.Construct ](); .CBO( #TRUE ); // 数値は、Big Endian で入出力 #pc "構築: ": this; } function ^JpegDecoder.Destruct() { #pc "破棄: ": this; .[ ::Buffer.Destruct ](); } function ^JpegDecoder.ProcMarker( Pixs, Xb ) { #pc "ProcMarker: ": Pixs, Xb; if( Pixs == null ) { .Seek( 0 ); .Read( Marker'USHORT ); #pc "Marker=": Marker'x(4); if( Marker != #SOI ) // 画像開始マーカーが最初に無ければエラー goto ERR_SOI; } else if( .MakeImage( Pixs, Xb ) != #TRUE ) goto FIN_OK; for( ;; ) { .Read( Marker'USHORT ); #pc "Marker=": Marker'x(4); switch( Marker ) { case #EOI: .ProcEOI(); quit; // 画像終了 case #DHT: .ProcDHT(); continue; // ハフマンテーブル定義 case #DQT: .ProcDQT(); continue; // 量子化テーブル定義 case #SOF: .ProcSOF(); continue; // フレーム開始(基本DCT方式) case #SOS: .ProcSOS(); quit; // スキャン開始 case #DRI: .ProcDRI(); continue; // リスタートインターバル定義 case #COM: .ProcCOM(); continue; // コメント default: if( Marker'in?( #RST_0, #RST_7 )) // リスタートインターバル終了 .ProcRST( Marker - #RST_0 ); else if( Marker'in?( #APP_0, #APP_F )) // アプリケーションデータセグメント .ProcAPP( Marker - #APP_0 ); else .OutMarker( Marker ); continue; } } FIN_OK: return #OK; OnFileEnd: .ErrMsg = "画像データの終端が不正です!"; goto FIN_NG; ERR_SOI: .ErrMsg = "画像開始マーカーがありません!"; goto FIN_NG; ERR_DHT: .ErrMsg = "ハフマンテーブル定義が不正!"; goto FIN_NG; ERR_DQT: .ErrMsg = "量子化テーブル定義が不正!"; goto FIN_NG; ERR_SOF: .ErrMsg = "フレーム開始ヘッダーが不正!"; goto FIN_NG; ERR_SOS: .ErrMsg = "スキャンヘッダーが不正!"; goto FIN_NG; ERR_NO_IMPL: .ErrMsg = "未対応のフォーマットです!"; goto FIN_NG; ERR_NO_HC: .ErrMsg = "ハフマンコードの復号不能!"; goto FIN_NG; ERR_ABORT: .ErrMsg = "解読不能のため中断しました!"; goto FIN_NG; FIN_NG: #pc .ErrMsg; return #NG; } function ^JpegDecoder.ProcEOI() { #pc "EOI: ": .Seek(), .Size(); warp FIN_OK; } function ^JpegDecoder.OutMarker( Marker ) { if( Marker == 0xFFC8 || Marker'in?( 0xFFF0, 0xFFFD ) ) { #p "将来用予約マーカー!": Marker'x(4); return; // これらのマーカーは無視 } if(( Marker & 0xFF00 ) == 0xFF00 ) { #p "想定外マーカー!": Marker'x(4); if( Marker'in?( 0xFFC1, 0xFFC3 ) ) // 基本DCT方式以外? warp ERR_NO_IMPL; } else #p "不正コード検出!": Marker'x(4); warp ERR_ABORT; } function ^JpegDecoder.ProcAPP( id ) // アプリケーションデータセグメント { .Read( La'USHORT ); // 本セグメントの長さ(バイト数) EndPos = .Seek() + La - 2; if( id == 0 && La >= 2 + 5 ) { .Read( ID'UBYTE(5) ); switch( s = ID'addr'ps( , "CP-1252" ) ) { case "JFIF", "JFXX": .JFIF = #TRUE; #pc "%s: ..."'fmt( s ); } // ここでは、JFIF, JFXX かどうかのみ確認(それ以外のデータは読み飛ばす) } .Seek( EndPos ); } function ^JpegDecoder.ProcCOM() // コメント { .Read( Lc'USHORT ); .Read( COM'UBYTE( Lc - 2 ) ); #pc "COM: %s"'fmt( COM'addr'ps( , "CP-1252" ) ); } function ^JpegDecoder.ProcDHT() // ハフマンテーブル定義を解読 { .Read( Lh'USHORT ); // 定義バイト数 #pc "DHT: ( Lh=%d )"'fmt( Lh ); EndPos = .Seek() + Lh - 2; for( ;; ) { .Read( tmp'UBYTE ); Tc = tmp >> 4; // テーブルクラス( =0: DC用, =1: AC用 ) Th = tmp & 0x0F; // テーブル種別 #pc " Tc=%d, Th=%d"'fmt( Tc, Th ); if( ! Tc'in?( 0, 1 ) || ! Th'in?( 0, 3 ) ) warp ERR_DHT; .Read( L'UBYTE(16) ); #pc " %2d %2d %2d %2d %2d %2d %2d %2d"'fmt( L(0),L(1),L(2),L(3),L(4),L(5),L(6),L(7) ); #pc " %2d %2d %2d %2d %2d %2d %2d %2d"'fmt( L(8),L(9),L(10),L(11),L(12),L(13),L(14),L(15) ); MinK = 0; delete H; for( i = 1 ; i <= 16 ; i++ ) // i: ハフマンコードのビット数 { MinK <<= 1; LimK = 01 << i; // 現ビット数 i で使えるビットパターンは、MinK から LimK 未満 if(( N = L( i - 1 )) == 0 ) // 現ビット数 i での値の個数 continue; .Read( V'UBYTE( N ) ); // 現ビット数 i での値を格納する配列 #px s = " %2d:"'fmt( i ); #px for( k = 0 ; k < N ; ) #px { #px s += " %3d"'fmt( V( k++ ) ); #px if( k % 8 == 0 || k >= N ) #px { #px #p s; #px s = " "; #px } #px } for( k = 0 ; k < N ; k++ ) { if( MinK >= LimK ) // 使えるビットパターンが無い? warp ERR_DHT; // ビット数とビットパターンに対応する値を設定 H[ i, MinK++ ] = V( k ); } } if( H'exist? ) .HT[ Tc, Th ] <- H; //#pc EndPos, .Seek(); switch(( EndPos - .Seek() )'sign ) { case 0: quit; case -1: warp ERR_DHT; // テーブル定義の終端位置が合わない? } } } function ^JpegDecoder.ProcDQT() // 量子化テーブル定義の読み出し { .Read( Lq'USHORT ); // 定義バイト数 #pc "DQT: ( Lq=%d, n=%g )"'fmt( Lq, ( Lq - 2 ) / ( 1.0 + 8 * 8 ) ); if( ( Lq - 2 ) % ( 1 + 8 * 8 ) != 0 ) warp ERR_DQT; for( t = ( Lq - 2 ) / ( 1 + 8 * 8 ) ; t > 0 ; t-- ) { .Read( tmp'UBYTE ); Pq = tmp >> 4; // 精度( =0: 8-bit ) Tq = tmp & 0x0F; // テーブル種別 if( Pq != 0 || ! Tq'in?( 0, 3 ) ) warp ERR_DQT; .Read( .QT[ Tq ]'UBYTE( 8*8 ) ); #pc " Tq=%d"'fmt( Tq ); #px q := .QT[ Tq ]; #px for( y = 0 ; y < 8 ; y++ ) #px #p " %3d %3d %3d %3d %3d %3d %3d %3d" #px 'fmt( q(8*y+0),q(8*y+1),q(8*y+2),q(8*y+3),q(8*y+4),q(8*y+5),q(8*y+6),q(8*y+7) ); } } function ^JpegDecoder.ProcDRI() // リスタートインターバル定義 { .Read( Lr'USHORT, .Ri'USHORT ); // .Ri: リスタートインターバル内の MCU の個数 #pc "DRI: Lr=%d, Ri=%d"'fmt( Lr, .Ri ); } function ^JpegDecoder.ProcRST( i ) // リスタートインターバル終了 { #pc "RST-%d:"'fmt( i ); } function ^JpegDecoder.ProcSOF() // フレーム開始(基本DCT方式)の読み出し { .SOF ::= { .Lf 'USHORT; // データ長(バイト数) .P 'UBYTE; // サンプル精度(ビット数)= 8 only .Y 'USHORT; // ライン数 .X 'USHORT; // ライン当たりのサンプル数 .Nf 'UBYTE; // 画像成分数(=以下の FEP の個数) } .Read( .SOF ); #pc "SOF: X=%d, Y=%d, Nf=%d ( Lf=%d, P=%d )"'fmt( .SOF.X, .SOF.Y, .SOF.Nf, .SOF.Lf, .SOF.P ); FEP ::= // フレーム成分指定パラメータ { .C 'UBYTE; // 成分識別子 .Sf 'BIT_FIELD(1); // サンプリングファクタ .Sf.H 'BIT(4); // 水平( = 1〜4 ) .Sf.V 'BIT(4); // 垂直( = 1〜4 ) .Tq 'UBYTE; // 量子化テーブルセレクタ( =0〜3 ) } if( .SOF.Lf != .SOF'size + FEP'size * .SOF.Nf || .SOF.P != 8 ) warp ERR_SOF; if( ! .SOF.Y'in?( 1, 4096 ) || ! .SOF.X'in?( 1, 4096 ) ) warp ERR_NO_IMPL; // このサイズを超える画像には未対応! if( .SOF.Nf != 3 && .SOF.Nf != 1 ) warp ERR_NO_IMPL; // 3要素カラーとモノクロ以外は未対応! for( i = 0 ; i < .SOF.Nf ; i++ ) { .Read( FEP ); #pc " %d: C=%d, H=%d, V=%d, Tq=%d"'fmt( i, FEP.C, FEP.Sf.H, FEP.Sf.V, FEP.Tq ); if( ! FEP.Sf.H'in?( 1, 4 ) || ! FEP.Sf.V'in?( 1, 4 ) || ! FEP.Tq'in?( 0, 3 ) ) warp ERR_SOF; .SOF.H [ FEP.C ] = FEP.Sf.H; .SOF.V [ FEP.C ] = FEP.Sf.V; .SOF.Tq[ FEP.C ] = FEP.Tq; } } function ^JpegDecoder.ProcSOS() // スキャン開始 { .Read( Ls'USHORT ); // スキャンヘッダー長(バイト数) EndPos = .Seek() + Ls - 2; .Read( .SOS.Ns'UBYTE ); // スキャン内の画素成分数( =1〜4 ) #pc "SOS: Ns=%d"'fmt( .SOS.Ns ); ESP ::= // 成分指定パラメータ { .Cs 'UBYTE; // スキャン成分セレクタ .Hs 'BIT_FIELD(1); // ハフマンテーブルセレクタ .Hs.Td 'BIT(4); // DC用( = 0,1 ) .Hs.Ta 'BIT(4); // AC用( = 0,1 ) } for( i = 0 ; i < .SOS.Ns ; i++ ) { .Read( ESP ); #pc " %d: Cs=%d, Td=%d, Ta=%d"'fmt( i, ESP.Cs, ESP.Hs.Td, ESP.Hs.Ta ); if( ! ESP.Hs.Td'in?( 0, 1 ) || ! ESP.Hs.Ta'in?( 0, 1 ) ) warp ERR_SOS; .SOS.Cs[i] = ESP.Cs; .SOS.Td[i] = ESP.Hs.Td; .SOS.Ta[i] = ESP.Hs.Ta; } .Read( Ss'UBYTE, Se'UBYTE, AhAl'UBYTE ); #pc " ( Ss=%d, Se=%d, AhAl=0x%02X )"'fmt( Ss, Se, AhAl ); if( EndPos != .Seek() ) // スキャンヘッダーの終端位置が合わない? warp ERR_SOS; } // エントロピー符号化データをデコードして、画像を生成 function ^JpegDecoder.MakeImage( Pixs, Xb ) { #pc "MakeImage: ": Pixs, Xb; Nf = .SOF.Nf; // 色成分数( =3: カラー, =1: モノクロ ) Xd = .SOF.X; Yd = .SOF.Y; MSFx = 0; // 横の最大サンプリングファクタ MSFy = 0; // 縦の最大サンプリングファクタ for( i = 0 ; i < Nf ; i++ ) { Cs = .SOS.Cs[ i ]; if( MSFx < .SOF.H[ Cs ] ) MSFx = .SOF.H[ Cs ]; if( MSFy < .SOF.V[ Cs ] ) MSFy = .SOF.V[ Cs ]; } #pc "MSFx=%d, MSFy=%d"'fmt( MSFx, MSFy ); NBx = ( Xd + 7 ) / 8; // 横のブロック数/画像 NBy = ( Yd + 7 ) / 8; // 縦のブロック数/画像 #pc "NBx=%d, NBy=%d"'fmt( NBx, NBy ); NUx = ( NBx + ( MSFx - 1 )) / MSFx; // 横のユニット数/画像 NUy = ( NBy + ( MSFy - 1 )) / MSFy; // 縦のユニット数/画像 #pc "NUx=%d, NUy=%d"'fmt( NUx, NUy ); NPx = NUx * MSFx * 8; // 横の画素数/画像 NPy = NUy * MSFy * 8; // 縦の画素数/画像 #pc "NPx=%d, NPy=%d"'fmt( NPx, NPy ); NMx = MSFx * 8; // 横の画素数/MCU NMy = MSFy * 8; // 縦の画素数/MCU #pc "NMx=%d, NMy=%d"'fmt( NMx, NMy ); Dv 'LONG( 3 ); // DC 成分の現差分値(各色成分毎) F 'LONG( 64 ); // 1ブロック(8x8)( 逆 DCT 変換前 ) for( i = 0 ; i < Nf ; i++ ) { Cs = .SOS.Cs[ i ]; MCU[ Cs ]'DOUBLE( NMx, NMy ); // 1MCUの各色成分 } .R 'UBYTE( NPx, NPy ); // 赤/画像 .G 'UBYTE( NPx, NPy ); // 緑/画像 .B 'UBYTE( NPx, NPy ); // 青/画像 Bv 'UBYTE; // バッファから読み出した現バイト値 Bp = 0; // 当バイト内の現ビット位置 ZI 'LONG( 64 ) = // ジグザグ走査用インデックス { 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63, }; // 逆 DCT 演算の前準備 #set PI 3.1415926535897 Cos := ::Math.cos; Sin := ::Math.sin; Round := ::Math.round; c14 = Cos( #PI / 4 ); s18 = Sin( #PI / 8 ); c18 = Cos( #PI / 8 ); s38 = Sin( #PI / 8 * 3 ); c38 = Cos( #PI / 8 * 3 ); s1h = Sin( #PI /16 ); c1h = Cos( #PI /16 ); s5h = Sin( #PI /16 * 5 ); c5h = Cos( #PI /16 * 5 ); s3h = Sin( #PI /16 * 3 ); c3h = Cos( #PI /16 * 3 ); s7h = Sin( #PI /16 * 7 ); c7h = Cos( #PI /16 * 7 ); T'DOUBLE( 64 ); S'DOUBLE( 64 ); // エントロピー符号化された MCU 列のデコード Nmcu = 0; for( Uy = 0 ; Uy < NUy ; Uy++ ) for( Ux = 0 ; Ux < NUx ; Ux++ ) // 各 MCU 毎に { for( Si = 0 ; Si < Nf ; Si++ ) // 各色成分( Y, Cr, Cb )毎に { //#pc "Ux=%d, Uy=%d, Si=%d"'fmt( Ux, Uy, Si ); Cs = .SOS.Cs[ Si ]; // Td = .SOS.Td[ Si ]; // Ta = .SOS.Ta[ Si ]; // //#pc "Cs=%d, Td=%d, Ta=%d"'fmt( Cs, Td, Ta ); Sfx = .SOF.H[ Cs ]; // 横方向のサンプリングファクタ Sfy = .SOF.V[ Cs ]; // 縦 〃 Tq = .SOF.Tq[ Cs ]; // Rx = MSFx / Sfx; // 横方向の画素反復回数 Ry = MSFy / Sfy; // 縦 〃 //#pc "Sfx=%d, Sfy=%d, Tq=%d, Rx=%d, Ry=%d"'fmt( Sfx, Sfy, Tq, Rx, Ry ); M := MCU[ Cs ]; // MCU 内の各ブロック毎に for( By = 0 ; By < Sfy ; By++ ) { Yo = By * 8; for( Bx = 0 ; Bx < Sfx ; Bx++ ) { Xo = Bx * 8; //#pc "-- Bx=%d, By=%d, Xo=%d, Yo=%d"'fmt( Bx, By, Xo, Yo ); call DecodeBlock; // エントロピー符号化ブロックのデコード } } //call ShowMCU; } call SetPixsAtMCU; Nmcu++; if( .StopReq ) return #FALSE; 'yield; } //call ShowRGB; return #TRUE; DecodeBlock: // エントロピー符号化された1ブロックのデコード F'MEMBLK'fill; // ブロック内の全要素を 0 に QT := .QT[ Tq ]; // DC 成分の設定 HT := .HT[ 0, Td ]; call GetHuffVal; call GetBitsVal; F(0) = ( Dv( Si ) += Bc ) * QT(0); // AC 成分の設定 HT := .HT[ 1, Ta ]; for( i = 1 ; i < 64 ; i++ ) { call GetHuffVal; if( Hv >= 0x10 ) // ランレングス ( Hv の上位 4-bit ) = 1〜15 { //i += ( Hv >> 4 ); // ランレングス個は 0 のまま i += ( Rn = Hv >> 4 ); // ランレングス個は 0 のまま if( i >= 64 ) break; Hv &= 0x0F; // Hv = 付加ビット数 // ZRL( =0xF0 ) の時は、以下で 0 がもう1つ追加される } else if( Hv == 0 ) // EOB ? break; // 残りは 0 のまま //else ... // ランレングス = 0, Hv = 付加ビット数 > 0 else Rn = 0; call GetBitsVal; //#pc "i=%d, ZI(i)=%d, Bc=%d, QT(i)=%d, Rn=%d, Hv=%d"'fmt( i, ZI(i), Bc, QT(i), Rn, Hv ); F( ZI(i) ) = Bc * QT(i); } //call ShowBlock; // 逆 DCT 変換 for( Y = 0 ; Y < 8*8 ; Y += 8 ) { t4 = F( 1 + Y ) * s1h - F( 7 + Y ) * s7h; t5 = F( 5 + Y ) * s5h - F( 3 + Y ) * s3h; t6 = F( 5 + Y ) * c5h + F( 3 + Y ) * c3h; t7 = F( 1 + Y ) * c1h + F( 7 + Y ) * c7h; u0 = F( 0 + Y ) * c14 + F( 4 + Y ) * c14; u1 = F( 0 + Y ) * c14 - F( 4 + Y ) * c14; u2 = F( 2 + Y ) * s18 - F( 6 + Y ) * s38; u3 = F( 2 + Y ) * c18 + F( 6 + Y ) * c38; u4 = t4 + t5; u5 = t4 - t5; u6 = -t6 + t7; u7 = t6 + t7; t0 = u0 + u3; t1 = u1 + u2; t2 = u1 - u2; t3 = u0 - u3; t4 = u4; t5 = -u5 * c14 + u6 * c14; t6 = u5 * c14 + u6 * c14; t7 = u7; T( 0 + Y ) = ( t0 + t7 ) / 2; T( 1 + Y ) = ( t1 + t6 ) / 2; T( 2 + Y ) = ( t2 + t5 ) / 2; T( 3 + Y ) = ( t3 + t4 ) / 2; T( 4 + Y ) = ( t3 - t4 ) / 2; T( 5 + Y ) = ( t2 - t5 ) / 2; T( 6 + Y ) = ( t1 - t6 ) / 2; T( 7 + Y ) = ( t0 - t7 ) / 2; } for( y = 0 ; y < 8 ; y++ ) { t4 = T( 1 * 8 + y ) * s1h - T( 7 * 8 + y ) * s7h; t5 = T( 5 * 8 + y ) * s5h - T( 3 * 8 + y ) * s3h; t6 = T( 5 * 8 + y ) * c5h + T( 3 * 8 + y ) * c3h; t7 = T( 1 * 8 + y ) * c1h + T( 7 * 8 + y ) * c7h; u0 = T( 0 * 8 + y ) * c14 + T( 4 * 8 + y ) * c14; u1 = T( 0 * 8 + y ) * c14 - T( 4 * 8 + y ) * c14; u2 = T( 2 * 8 + y ) * s18 - T( 6 * 8 + y ) * s38; u3 = T( 2 * 8 + y ) * c18 + T( 6 * 8 + y ) * c38; u4 = t4 + t5; u5 = t4 - t5; u6 = -t6 + t7; u7 = t6 + t7; t0 = u0 + u3; t1 = u1 + u2; t2 = u1 - u2; t3 = u0 - u3; t4 = u4; t5 = -u5 * c14 + u6 * c14; t6 = u5 * c14 + u6 * c14; t7 = u7; S( 0 * 8 + y ) = ( t0 + t7 ) / 2 + 128; S( 1 * 8 + y ) = ( t1 + t6 ) / 2 + 128; S( 2 * 8 + y ) = ( t2 + t5 ) / 2 + 128; S( 3 * 8 + y ) = ( t3 + t4 ) / 2 + 128; S( 4 * 8 + y ) = ( t3 - t4 ) / 2 + 128; S( 5 * 8 + y ) = ( t2 - t5 ) / 2 + 128; S( 6 * 8 + y ) = ( t1 - t6 ) / 2 + 128; S( 7 * 8 + y ) = ( t0 - t7 ) / 2 + 128; } // MCUに配置(間引き解消) Ym = Yo; for( Y = 0 ; Y < 8*8 ; Y += 8 ) { Xm = Xo; for( x = 0 ; x < 8 ; x++ ) { for( j = 0 ; j < Ry ; j++ ) for( i = 0 ; i < Rx ; i++ ) M( Xm + i, Ym + j ) = S( x + Y ); Xm += Rx; } Ym += Ry; } back; GetHuffVal: // ハフマンコードを解読して、Hv に設定 Bc = 0; for( n = 1 ;; n++ ) { call GetToAddBit; if( HT[ n, Bc ]'exist? ) { Hv = HT[ n, Bc ]; // 区間番号(ビット数) break; } if( n >= 16 ) warp ERR_NO_HC; } //#pc " Hv=%d"'fmt( Hv ); back; GetBitsVal: // Hv ビットだけ読み出して解読し、Bc に設定 Bc = 0; for( n = 1 ; n <= Hv ; n++ ) call GetToAddBit; //#pc " 0: Bc=%d ( Hv=%d )"'fmt( Bc, Hv ); if( Bc < ( 1 << ( Hv - 1 ))) Bc -= ( 1 << Hv ) - 1; //#pc " 1: Bc=%d ( Hv=%d )"'fmt( Bc, Hv ); back; //( Hv = 0 の時は、Bc = 0 になる ) GetToAddBit: // 1ビットを読み出して、Bc の LSB から追加 if( --Bp < 0 ) { .Read( Bv ); if( Bv == 0xFF ) { .Read( Bt'UBYTE ); if( Bt == 0 ) ; // 0xFF の次の 0x00 は読み飛ばす else if( Bt'in?( 0xD0, 0xD7 ) ) // リスタートインターバル終了? { #pc "RST: ": Bv'x(2), Bt'x(2), n, Nmcu; if( Nmcu % .Ri != 0 || Nmcu / .Ri % 8 != ( Bt + 1 ) & 0x07 ) #p "リスタートインターバル不正!", Nmcu, .Ri, Bt'x(2) ; n = 1; Bc = 0; .Read( Bv ); Dv(0) = Dv(1) = Dv(2) = 0; } else //(この場合は無い筈だが念のため) #pc "@@@ ", Bv'x(2), Bt'x(2), n, Nmcu, .Seek( -1, "cur" ); } Bp = 7; } Bc = ( Bc << 1 ) | (( Bv >> Bp ) & 01 ); back; SetPixsAtMCU: // 現 MCU を BMP の画像バッファ内の対応位置に設定 Ye = Yd - 1; Ym = Uy * NMy; Xmo = Ux * NMx; switch( Nf ) { case 1: // モノクロの場合 C0 := MCU[ .SOS.Cs[0] ]; for( y = 0 ; y < NMy && Ym < Yd ; y++, Ym++ ) { Xm = Xmo; i = ( Ye - Ym ) * Xb + Xm * 3; for( x = 0 ; x < NMx && Xm < Xd ; x++, Xm++ ) { Pixs( i ) = Pixs( i + 1 ) = Pixs( i + 2 ) = Round( C0( x, y ))'cut( 0, 255 ); i += 3; } } break; case 3: // カラーの場合 C0 := MCU[ .SOS.Cs[0] ]; C1 := MCU[ .SOS.Cs[1] ]; C2 := MCU[ .SOS.Cs[2] ]; for( y = 0 ; y < NMx && Ym < Yd ; y++, Ym++ ) { Xm = Xmo; i = ( Ye - Ym ) * Xb + Xm * 3; for( x = 0 ; x < NMx && Xm < Xd ; x++, Xm++ ) { Y = C0( x, y ); Cr = C1( x, y ) - 128; Cb = C2( x, y ) - 128; Pixs( i ) = Round( Y + 1.40200 * Cr )'cut( 0, 255 ); Pixs( i + 1 ) = Round( Y - 0.34414 * Cb - 0.71414 * Cr )'cut( 0, 255 ); Pixs( i + 2 ) = Round( Y + 1.77200 * Cb )'cut( 0, 255 ); i += 3; } } break; } back; OnError: #pc "@@@ ": i, ::ErrorMessage( $err_code, $err_info ); back; /*@@@@@*/ ShowMCU: #pc "Ux=%d, Uy=%d, Si=%d: "'fmt( Ux, Uy, Si ); for( y = 0 ; y < NMy ; y++ ) { s = ""; for( x = 0 ; x < NMx ; x++ ) { s += " %02X"'fmt( Round( M( x, y ))'int'cut( 0, 255 ) ); // s += " %3.1f"'fmt( M( x, y ) ); } #p s; } back; ShowBlock: #pc "Si=%d, Uy=%d, Ux=%d, By=%d, Bx=%d: "'fmt( Si, Uy, Ux, By, Bx ); for( y = 0 ; y < 8 ; y++ ) { s = ""; for( x = 0 ; x < 8 ; x++ ) { s += " %4d"'fmt( F( y * 8 + x ) ); } #p s; } back; /*@@@@@*/ } //-----------------------------------------------------------------------------