2013-09-26 3 views
2

작은 워터 마크 프로그램을 작성하여 이미지에 사용자 지정 워터 마크를 추가했습니다. 흰색과 검은 색 두 개의 워터 마크가 있습니다. 워터 마크는 항상 이미지의 왼쪽 아래에 배치됩니다. 이미지의 해당 영역을 복제하여 해당 위치 (밝은 영역에서는 검은 색 워터 마크, 어두운 영역에서는 흰색 워터 마크)를 기반으로 어떤 워터 마크를 배치해야하는지 결정합니다.비트 맵 클론에서 WinForms 메모리 부족 예외

내 컴퓨터에서 응용 프로그램 (디버그 또는 일반)을 사용할 때 - 아무런 문제가 없습니다. 모든 이미지가 처리되고 올바른 위치에 워터 마크가 추가됩니다.

그러나 클라이언트 컴퓨터에서는 프로그램이 복제 부분에 OutOfMemory 예외를 발생시키는 모든 이미지에서 중단됩니다.

OutOfMemory Exception은 경계를 벗어난 영역을 지정할 때도 throw됩니다.하지만이 함수는 내 컴퓨터에서 매력처럼 작동하므로이 경우를 상상할 수 없습니다. 게다가, 몇 번 시도해도 프로그램이 중단되지 않습니다. 복제하려는 모든 시도가 중단됩니다.

텍스트 (DrawString 메서드)가 있는지 여부는 중요하지 않습니다. 클론에서 깨진다.

처리되는 이미지는 크지 만 "거대한"(최대 6016 x 4000 픽셀)은 아니지만 더 작은 이미지 (3264 x 2448 픽셀)로도 클라이언트가 중단됩니다.

변수 :

bmOriginal : 원래 비트 맵

processImage - : 워터 마크 아래 추가 정보 입력란

black 원 화상 (의 PictureBox) bmOriginal

watermarkText의 비트 맵 카피 및 white : 워터 마크 이미지가 포함 된 그림 상자

watermarkCombo : 자동 흰색 또는 검은 색 선택 콤보 박스 (자동 실패)

코드 : 이제 using (Bitmap watermarkClone = bmOriginal.Clone(watermarkArea, bmOriginal.PixelFormat))

큰 질문 : 내가 없애 어떻게 오류의

using (Graphics gWatermark = Graphics.FromImage(bmOriginal)) 
{ 
    gWatermark.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; 
    System.Drawing.SolidBrush drawBrush = new System.Drawing.SolidBrush(System.Drawing.Color.Black); 

    // position watermark - watermark should be 10% of the image height 
    int watermarkHeight = (int)(processImage.Image.Height * 0.1); 
    int watermarkPadding = (int)(watermarkHeight * 0.1); // not completely true, but asume watermark is square 
    Rectangle watermarkArea = new Rectangle(watermarkPadding, processImage.Image.Height - (watermarkPadding + (watermarkText.Text.Length == 0 ? 0 : watermarkPadding) + watermarkHeight), watermarkHeight, watermarkHeight); 

    // determine color watermark 
    bmWatermark = (Bitmap)black.Image; 
    if (watermarkCombo.SelectedIndex == 0) 
    { 
     using (Bitmap watermarkClone = bmOriginal.Clone(watermarkArea, bmOriginal.PixelFormat)) 
     { 
      var pixels = Pixels(watermarkClone); 
      if (pixels.Average((Func<Color, decimal>)Intensity) < 110) // human eye adoption; normal threshold should be 128 
      { 
       bmWatermark = (Bitmap)white.Image; 
       drawBrush = new System.Drawing.SolidBrush(System.Drawing.Color.White); 
      } 
     } 
    } 
    else if (watermarkCombo.SelectedIndex == 1) 
    { 
     bmWatermark = (Bitmap)white.Image; 
     drawBrush = new System.Drawing.SolidBrush(System.Drawing.Color.White); 
    } 

    // draw the watermark 
    gWatermark.DrawImage(bmWatermark, watermarkArea.X, watermarkArea.Y, watermarkArea.Width, watermarkArea.Height); 

    // draw the text (if needed) 
    if (watermarkText.Text.Length > 0) 
    { 
     System.Drawing.Font drawFont = new System.Drawing.Font("Tahoma", (float)watermarkPadding); 
     gWatermark.DrawString(watermarkText.Text, drawFont, drawBrush, watermarkPadding, bmOriginal.Height - (watermarkPadding * 2)); 
    } 
} 
bmOriginal.Save(System.IO.Path.Combine(diWatermarked.FullName, fileName), System.Drawing.Imaging.ImageFormat.Jpeg); 

라인 그 OutOfMemory 예외 .. 누구의 아이디어?

편집 내가 자동으로 워터 마크의 색상을 결정하고 바로 워터 마크를 추가하지 않도록 선택 일반적으로 프로그램 기능 (의는 흰색 하나를 가정 해 봅시다). 오류 로그에서 스택 추적을 보았습니다 (예외를 출력하고 모든 내부 예외가있는 함수를 포착 함).

범위를 벗어난 영역을 지정할 때 복제 기능을 사용하는 많은 OOM 예외가 발생한다는 것을 알고 있습니다. 하지만 여기서는 그렇지 않습니다.

디버그 모드에서 응용 프로그램을 사용할 때이 메모리에서 볼 때, 나는 5.36 기가 프로그램에서 시작 시작하고 내가 언급 실행을 실행할 때 (5.42 기가의 최대 스파이크) 정규화 5.39 (GB)는 그렇지 않아 미친 듯이 추측하는 기억.

내가 사용하는 코드는 평균 "색상"을 결정합니다 (StackOverflow에있는 사람의 것입니다 - 다른 일부 대답에서 복사했으나 링크를 찾을 수 없음).

// functions used to determine watermark color 
private static decimal ComponentAverage(decimal a, decimal b) 
{ 
    return Math.Min(a, b) + Math.Abs(a - b)/2M; 
} 
private static decimal Intensity(Color color) 
{ 
    decimal result = color.A; 
    result = ComponentAverage(result, color.R); 
    result = ComponentAverage(result, color.G); 
    result = ComponentAverage(result, color.B); 
    return result; 
} 
private static IEnumerable<Color> Pixels(Bitmap bitmap) 
{ 
    for (int x = 0; x < bitmap.Width; x++) 
     for (int y = 0; y < bitmap.Height; y++) 
      yield return bitmap.GetPixel(x, y); 
} 

SOURCE 여기에 테스트 프로젝트가있다 : http://hotpepper.nu/oomtestapp.zip

+0

으로 나누어되지 않았기 때문에 마지막 타일 오류 것 응용 프로그램은 런타임에 필요를 충족시키기에 충분한 메모리를 할당 할 수 없으므로 경계 확인과는 분명히 다르지 않습니다. –

+1

일반적으로 개체를 처리하지 않으면 GDI를 사용하여이 작업을 쉽게 실행할 수 있습니다. 다른 이미지에 대한 참조가 많고 코드에없는 내용이있을 수 있습니다. 그 이유는 간단합니다. – MichaC

+0

코드는 테스트 할 수 없으며 문제를 재현 할 수 없습니다. – MichaC

답변

1

레온, 당신이 모든 자원을 잠그지에 업로드 한 코드를 변경했습니다. 응용 프로그램을 열어두면 일부 파일이 사용 중이므로 출력 폴더를 삭제할 수 없다는 사실을 알고 있습니다. 이것은 일반적으로 모든 파일 핸들을 해제하지 않았 음을 의미합니다. 기본적으로 항상 마지막 파일입니다.

변경 전후의 컴퓨터에서 메모리 부족 문제를 재현하지 못했습니다. 아마도 용량이 큰 파일에 문제가있는 것 같습니다.

어쨌든 Ok. ImageBox를 사용하여 흰색 및 검정색 리소스를로드하고 디스크에서 이미지를로드하는 것으로 나타났습니다. 이 전혀 필요하지 않습니다, intead 디스크에서 이미지를로드 할 자원 그리고

Bitmap white = OomTestApp.Properties.Resources.white; 
Bitmap black = OomTestApp.Properties.Resources.black; 

직접

를 사용하여 간단하게 Bitmap.FromFile

내가 제대로 .Dispose없는 using 당신의 자원을 해제하기 위해 몇 줄을 추가 한 사용 블록이 사용됩니다.

그리고 내가 원래 이미지의 픽셀을 계산하고 그 시점에서 그 이미지에 무언가를 그리지 않기 때문에 절대적으로 필요하지 않기 때문에 Clone() 호출을 제거했습니다. 그렇다면 이미지를 복제해야 할 필요는 무엇입니까?

여기에 전체 코드가

if (errors == 0) 
{ 
this.Height = 323; 
goButton.Enabled = false; 
stopButton.Enabled = true; 

Bitmap white = OomTestApp.Properties.Resources.white; 
Bitmap black = OomTestApp.Properties.Resources.black; 
Bitmap bmWatermark = black; 
Bitmap processImage = null; 


progressBar1.Maximum = filesToProcess.Count; 

foreach (string handleFile in filesToProcess) 
{ 
    string fileName = System.IO.Path.GetFileName(handleFile); 
    fileNameLabel.Text = "File: " + System.IO.Path.GetFileName(handleFile); 
    try 
    { 
     // create backup if checked 
     if (diOriginal != null) 
     { 
      System.IO.File.Move(handleFile, System.IO.Path.Combine(diOriginal.FullName, fileName)); 
      processImage = (Bitmap)Bitmap.FromFile(System.IO.Path.Combine(diOriginal.FullName, fileName)); 
     } 
     else 
     { 
      processImage = (Bitmap)Bitmap.FromFile(handleFile); 
     } 

     double aspectRatio = (double)processImage.Width/(double)processImage.Height; 

     using (Graphics gWatermark = Graphics.FromImage(processImage)) 
     { 
      gWatermark.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; 
      System.Drawing.SolidBrush drawBrush = new System.Drawing.SolidBrush(System.Drawing.Color.Black); 

      // position watermark - watermark should be 10% of the image height 
      int watermarkHeight = (int)(processImage.Height * 0.1); 
      int watermarkPadding = (int)(watermarkHeight * 0.1); // not completely true, but asume watermark is square 
      // calculate rectangle. if there is extra text, add extra padding below 
      Rectangle watermarkArea = new Rectangle(watermarkPadding, processImage.Height - (watermarkPadding + (watermarkText.Text.Length == 0 ? 0 : watermarkPadding) + watermarkHeight), watermarkHeight, watermarkHeight); 

      // determine color watermark 
      bmWatermark = black; 
      if (watermarkCombo.SelectedIndex == 0) 
      { 
       /*using (Bitmap watermarkClone = processImage.Clone(watermarkArea, processImage.PixelFormat)) 
       {*/ 
       var pixels = Pixels(processImage); 
       if (pixels.Average((Func<Color, decimal>)Intensity) < 110) // human eye adoption; normal threshold should be 128 
       { 
        bmWatermark = white; 
        drawBrush = new System.Drawing.SolidBrush(System.Drawing.Color.White); 
       } 
       //} 
      } 
      else if (watermarkCombo.SelectedIndex == 1) 
      { 
       bmWatermark = white; 
       drawBrush = new System.Drawing.SolidBrush(System.Drawing.Color.White); 
      } 

      // draw the watermark 
      gWatermark.DrawImage(bmWatermark, watermarkArea.X, watermarkArea.Y, watermarkArea.Width, watermarkArea.Height); 

      // draw the text (if needed) 
      if (watermarkText.Text.Length > 0) 
      { 
       System.Drawing.Font drawFont = new System.Drawing.Font("Tahoma", (float)watermarkPadding); 
       gWatermark.DrawString(watermarkText.Text, drawFont, drawBrush, watermarkPadding, processImage.Height - (watermarkPadding * 2)); 
       drawFont.Dispose(); 
      } 
      // disposing resources 
      drawBrush.Dispose(); 

     } 
     // save the watermarked file 
     processImage.Save(System.IO.Path.Combine(diWatermarked.FullName, fileName), System.Drawing.Imaging.ImageFormat.Jpeg); 


     // stop button pressed? 
     Application.DoEvents(); 
     if (stopProcess) break; 

     // update exection progress 
     progressBar1.Value++; 
     percentLabel.Text = ((int)((progressBar1.Value * 100)/filesToProcess.Count)).ToString() + "%"; 
     fileCountLabel.Text = "File " + progressBar1.Value.ToString() + "/" + filesToProcess.Count.ToString(); 
    } 
    catch (Exception ex) 
    { 
     try 
     { 
      using (System.IO.StreamWriter sw = new System.IO.StreamWriter(System.IO.Path.Combine(folderText.Text, "errorlog.txt"), true)) 
      { 
       sw.WriteLine("File: " + fileName); 
       while (ex != null) 
       { 
        sw.WriteLine("Message: " + ex.Message); 
        sw.WriteLine(ex.StackTrace); 
        sw.WriteLine(ex.Source); 
        ex = ex.InnerException; 
       } 
       sw.WriteLine(); 
      } 
     } 
     catch 
     { 
      // nothing to do - it already failed 
     } 
     errors++; 
    } 
    finally 
    { 
     if (processImage != null) processImage.Dispose(); 
    } 
} 

// dispose resources 
white.Dispose(); 
black.Dispose(); 
bmWatermark.Dispose(); 



if (!stopProcess) 
{ 
    // set status to complete 
    fileCountLabel.Text = "File " + filesToProcess.Count.ToString() + "/" + filesToProcess.Count.ToString(); 
    percentLabel.Text = "100%"; 
    fileNameLabel.Text = "Completed..."; 
} 
else 
{ 
    fileNameLabel.Text = "Aborted..."; 
} 

fileNameLabel.Text += errors.ToString() + " error(s) encountered"; 

// defaults to screen 
progressBar1.Value = progressBar1.Maximum; 
stopProcess = false; 
goButton.Enabled = true; 
stopButton.Enabled = false; 
+0

답변 해 주셔서 감사합니다. 복제본을 사용한 이유는 워터 마크가 위치하는 이미지 부분을보고 싶기 때문입니다. 어두운 영역 인 경우 밝은 색 워터 마크를 배치하십시오. 그러나 변경 사항을 통합하여 프로그램이 작동하는지 확인하겠습니다. – Leon

+0

괜찮 았어, 그 부분을 무시하고 다시 댓글을 달아주세요.) – MichaC

+0

기본적으로 열려있는 파일 핸들이 내 OOM의 원인이었습니다. 도움에 감사드립니다! – Leon

-1

글쎄, 나는 페이지에있는 모든 것을 읽어 보지 않았 (당신은 폴더를 만든 후 시작),하지만 누군가가 비트 맵의 ​​"걸음"을 언급 있을까? 기본적으로 비트 맵은 4 바이트의 배수 여야합니다. 타일을 만들기 위해 그리드를 분해하는 데 문제가있었습니다. 나는, ussually의에서 OutOfMemory 예외가 발생합니다 알고 * 내가 범위를 벗어 * 가`OutOfMemoryException`이 경우에 슬로우됩니다 영역을 지정할 때 비트 맵 4.

The stride is the width of a single row of pixels (a scan line), rounded up to a four-byte boundary. If the stride is positive, the bitmap is top-down. If the stride is negative, the bitmap is bottom-up. https://softwarebydefault.com/2013/04/11/bitmap-color-balance/