none
画像印刷時にメモリー不足表示発生の対処方法について RRS feed

  • 質問

  • Visual Studio vb.net 2012で開発を行っております。

    1ページに4枚の画像を含むページを診察する場合、60ページ程の場合は問題なく印刷が可能ですが、

    120ページになりますと「メモリー不足エラーが発生します。」、対処方法を教えていただけますでしょうか。

    利用環境:windoows 8.1GBメモリ

    印刷のコード:

        '印刷の開始
        Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click

            '印刷ダイアログボックス
            Dim ret As DialogResult

            PrintDialog1.Document = PrintDocument1
            PrintDialog1.AllowSomePages = True
            ret = PrintDialog1.ShowDialog

            If ret = Windows.Forms.DialogResult.OK Then
                PrintDocument1.Print()
            End If

        End Sub

    エラー内容:

    ************** 例外テキスト **************
    System.OutOfMemoryException: メモリが不足しています。
       場所 System.Drawing.Image.FromFile(String filename, Boolean useEmbeddedColorManagement)
       場所 WinAppConstructions.FormMain.photoPagePrint(Graphics g, PhotoWrapper photos, Int32 page)
       場所 WinAppConstructions.FormMain.PrintDocument1_PrintPage(Object sender, PrintPageEventArgs e)
       場所 System.Drawing.Printing.PrintDocument.OnPrintPage(PrintPageEventArgs e)
       場所 System.Drawing.Printing.PrintDocument._OnPrintPage(PrintPageEventArgs e)
       場所 System.Drawing.Printing.PrintController.PrintLoop(PrintDocument document)
       場所 System.Drawing.Printing.PrintController.Print(PrintDocument document)
       場所 System.Drawing.Printing.PrintDocument.Print()
       場所 WinAppConstructions.FormMain.Button3_Click(Object sender, EventArgs e)
       場所 System.Windows.Forms.Control.OnClick(EventArgs e)
       場所 System.Windows.Forms.Button.OnClick(EventArgs e)
       場所 System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
       場所 System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
       場所 System.Windows.Forms.Control.WndProc(Message& m)
       場所 System.Windows.Forms.ButtonBase.WndProc(Message& m)
       場所 System.Windows.Forms.Button.WndProc(Message& m)
       場所 System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
       場所 System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
       場所 System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

    よろしくお願いいたします。

     

    2015年2月28日 3:24

回答

  • こんにちは。

    Using句を使うなど細かいところはいろいろあるのですが、

    img = Image.FromFile(photo.path)        '画像表示化
    
    h = size_h / img.Height                 '画像高さの比率
    w = img.Width * h                       '画像幅
    
    g.DrawImage(img, x, y, w, size_h)
    img.Dispose()

    不要になった時点でimgオブジェクトをDisposeします。
    ここではおそらく、GCを明示的に呼び出す必要は無いでしょう。

    さて、「ページ数が増えると発生する」と仰っているので可能性は低いかもしれませんが、
    https://msdn.microsoft.com/ja-jp/library/stf701f5(v=vs.110).aspx

    上記では、「ファイルのイメージ形式が有効でない場合、または、そのファイルのピクセル形式が GDI+ でサポートされていない場合、このメソッドは OutOfMemoryException 例外をスローします。」ともあります。

    Disposeで解決しなければ、読み込むファイルがすべて有効なファイルであるかも確認してみてください。
    無効なイメージ形式の場合でもこの例外は「メモリが不足しています」となります。



    2015年3月2日 14:42
    モデレータ
  • 自分で確保した、生成したなど、自身に解放の責務がある場合は Dispose を呼ぶべきですが、この PrintPage イベントの e.Graphics に対して Dispose を呼ぶのは避けた方が良いでしょう。
    呼び出し元が確保した Graphics ですので、呼び出し元が再利用する可能性も否定できる材料がないからです。

    「何にでも Dispose を呼べば良い」と捉えていると、アプリケーションが正しく動かなくなるかもしれません。
    なぜ Dispose が必要なのか、どういった場面では必要なのか、そのメカニズムを理解するように努めた方が良いでしょう。
    (それ故に、私はコードを提示しませんでした)
    2015年3月2日 15:26

すべての返信

  • PrintDocument1_PrintPage や photoPagePrint の作りによる可能性もあるので、現状で的確なコメントは難しいのではないでしょうか。

    念のため。Dispose はされているのですよね?

    2015年2月28日 6:23
  • 情報不足で申し訳ございません。

    下記に、追記いたします。

    また、Dispose処理は行っておりません。

    どのように処理を行えばよろしいでしょうか。教えていただけますでしょうか。

    どうぞ、よろしくお願いいたします。

        '印刷処理
        Private Sub PrintDocument1_PrintPage(sender As Object, e As Printing.PrintPageEventArgs) Handles PrintDocument1.PrintPage

            Dim g As Graphics = e.Graphics
            Static photos As PhotoWrapper = New PhotoWrapper

            '描画単位をミリメートル
            g.PageUnit = GraphicsUnit.Millimeter

            '工事一覧の選択レコード
            Dim id = BindingSource1.Position
            '工事情報が無い場合中止
            If id = -1 Then
                Return
            End If

            '連番 Id を取得
            Dim val = DataGridView1.Item(0, id).Value
            Dim cont As Cont = getConstruction(val)                 '工事情報DB取得

            If page = 0 Then
                photos = getPhotos(val)                             '写真登録DB取得
                topPagePrint(g, val)                                '表紙を印刷

                count = photos.Count                                '写真登録数
                count = Math.Ceiling(count / cont.titles_pnumber)   '写真枚数と表示数を計算し切り上げ
                If count > 0 Then
                    e.HasMorePages = True
                End If

            Else

                photoPagePrint(g, photos, page)

                If page >= count Then
                    e.HasMorePages = False
                Else
                    e.HasMorePages = True
                End If

            End If

            page += 1


        End Sub


        '写真印刷
        Public Sub photoPagePrint(g As Graphics, photos As PhotoWrapper, page As Integer)

            Dim strPage = page & "/" & count
            g.DrawString(strPage, New Font("MS ゴシック", 20, FontStyle.Bold Or FontStyle.Italic), Brushes.Black, New Point(180, 10))

            Dim photo As Photo
            Dim img As Image
            Dim h As Single = 0, w = 0, size_h = 0, photo_titel = 0
            Dim x = 30, y = 20
            Dim start = 0

            photo_titel = My.Settings.photo_titel_x     'タイトル表示の横位置

            If photoCount = 3 Then      '画像3枚表示
                size_h = My.Settings.photo3_h               '画像の高さ
                start = (page - 1) * 3                      '画像取得開始レコード(インデックス)

                For i As Integer = 0 To 2 Step 1

                    If photos.Count <= (start + i) Then     '写真登録数によって中止
                        Exit For
                    End If

                    photo = photos.Item(start + i)          '登録画像情報DB取得
                    img = Image.FromFile(photo.path)        '画像表示化

                    h = size_h / img.Height                 '画像高さの比率
                    w = img.Width * h                       '画像幅

                    g.DrawImage(img, x, y, w, size_h)
                    g.DrawString("No." & photo.titles_title, New Font("MS 明朝", 18, FontStyle.Regular), Brushes.Black, New Point(photo_titel, y))
                    g.DrawString(photo.titles_name, New Font("MS 明朝", 18, FontStyle.Regular), Brushes.Black, New Point(photo_titel, y + 10))

                    g.DrawString(photo.etc, New Font("MS 明朝", 10, FontStyle.Regular), Brushes.Black, New Point(x, y + size_h + 2))
                    g.DrawString(photo.etc2, New Font("MS 明朝", 10, FontStyle.Regular), Brushes.Black, New Point(x, y + size_h + 7))

                    y += size_h + 20

                Next

            ElseIf photoCount = 4 Then
                size_h = My.Settings.photo4_h               '画像の高さ
                start = (page - 1) * 4                      '画像取得開始レコード(インデックス)

                For i As Integer = 0 To 3 Step 1

                    If photos.Count <= (start + i) Then     '写真登録数によって中止
                        Exit For
                    End If

                    photo = photos.Item(start + i)          '登録画像情報DB取得
                    img = Image.FromFile(photo.path)        '画像表示化

                    h = size_h / img.Height                 '画像高さの比率
                    w = img.Width * h                       '画像幅

                    g.DrawImage(img, x, y, w, size_h)
                    g.DrawString("No." & photo.titles_title, New Font("MS 明朝", 18, FontStyle.Regular), Brushes.Black, New Point(photo_titel, y))
                    g.DrawString(photo.titles_name, New Font("MS 明朝", 18, FontStyle.Regular), Brushes.Black, New Point(photo_titel, y + 10))

                    g.DrawString(photo.etc, New Font("MS 明朝", 10, FontStyle.Regular), Brushes.Black, New Point(x, y + size_h + 2))
                    g.DrawString(photo.etc2, New Font("MS 明朝", 10, FontStyle.Regular), Brushes.Black, New Point(x, y + size_h + 7))

                    y += size_h + 20

                Next

            End If


        End Sub


    2015年2月28日 15:23
  • Image.FromFile で得られる Image オブジェクトは IDisposable を実現しており、基本的に不要になったら Dispose を呼ぶべきです。
    今回の場合、メモリを解放させる役割を持っている Dispose を呼んでいないのでエラーになったのでしょう。

    どこのタイミングで Dispose するのが安全かきちんと確かめていないのですが、DrawImage の後ですかね?
    (間違っていたらすみません)

    2015年2月28日 23:28
  • ご回答いただきまして、ありがとうございます。

     Disposeの呼び出し方法は下記のように行い、DrawImage の後に実行する様に致します。

    ( Disposeの呼び出し方法は、あまり詳しくないのですが)

     System.GC.Collect()
     System.GC.WaitForPendingFinalizers()


    2015年3月2日 13:22
  •  Disposeの呼び出し方法は下記のように行い、DrawImage の後に実行する様に致します。

    ( Disposeの呼び出し方法は、あまり詳しくないのですが)

     System.GC.Collect()
     System.GC.WaitForPendingFinalizers()

    それらは Dispose ではありません。
    ファイナライズと Dispose は別物なので、この機会に .NET のメモリ管理についておさらいしていただいた方がよいでしょう。

    https://msdn.microsoft.com/ja-jp/library/bb985010.aspx
    https://msdn.microsoft.com/ja-jp/library/dd297765.aspx

    2015年3月2日 13:50
  • こんにちは。

    Using句を使うなど細かいところはいろいろあるのですが、

    img = Image.FromFile(photo.path)        '画像表示化
    
    h = size_h / img.Height                 '画像高さの比率
    w = img.Width * h                       '画像幅
    
    g.DrawImage(img, x, y, w, size_h)
    img.Dispose()

    不要になった時点でimgオブジェクトをDisposeします。
    ここではおそらく、GCを明示的に呼び出す必要は無いでしょう。

    さて、「ページ数が増えると発生する」と仰っているので可能性は低いかもしれませんが、
    https://msdn.microsoft.com/ja-jp/library/stf701f5(v=vs.110).aspx

    上記では、「ファイルのイメージ形式が有効でない場合、または、そのファイルのピクセル形式が GDI+ でサポートされていない場合、このメソッドは OutOfMemoryException 例外をスローします。」ともあります。

    Disposeで解決しなければ、読み込むファイルがすべて有効なファイルであるかも確認してみてください。
    無効なイメージ形式の場合でもこの例外は「メモリが不足しています」となります。



    2015年3月2日 14:42
    モデレータ
  • ご回答ありがとうございます。

    早速、確認してみました。

    g.DrawImage(img, x, y, w, size_h)
    img.Dispose()

    また、下記の内容も追加いたしました。

     '印刷処理
        Private Sub PrintDocument1_PrintPage(sender As Object, e As Printing.PrintPageEventArgs) Handles PrintDocument1.PrintPage
            Dim g As Graphics = e.Graphics
            Static photos As PhotoWrapper = New PhotoWrapper
            '描画単位をミリメートル
            g.PageUnit = GraphicsUnit.Millimeter

    '省略

            g.Dispose()       'メモリ解放

        End Sub

    手元に、テストデータが、120ページ分無く確認できませんが、後程、確認する予定です。

    色々、ご丁寧に教えていただきまして、誠にありがとうございました。

    どうぞ、どうぞよろしくお願いいたします。


    2015年3月2日 15:11
  • 自分で確保した、生成したなど、自身に解放の責務がある場合は Dispose を呼ぶべきですが、この PrintPage イベントの e.Graphics に対して Dispose を呼ぶのは避けた方が良いでしょう。
    呼び出し元が確保した Graphics ですので、呼び出し元が再利用する可能性も否定できる材料がないからです。

    「何にでも Dispose を呼べば良い」と捉えていると、アプリケーションが正しく動かなくなるかもしれません。
    なぜ Dispose が必要なのか、どういった場面では必要なのか、そのメカニズムを理解するように努めた方が良いでしょう。
    (それ故に、私はコードを提示しませんでした)
    2015年3月2日 15:26
  • ご回答ありがとうございます。

    e.Graphics に対して Disposeの呼び出しは行わないようにいたします。

    そうぞ、よろしくお願いいたします。

    2015年3月2日 15:31