PictureBox에 인터넷 이미지를 띄우거나, 상품 사진 URL 목록을 받아 썸네일을 만드는 프로그램을 짜다 보면 "URL을 넣으면 Bitmap이 나오는" 함수 하나가 꼭 필요해집니다. 파일로 저장했다가 다시 읽을 필요 없이 메모리에서 바로 이미지 객체로 만드는 방법입니다.
public Bitmap WebImageView(string URL)
{
try
{
WebClient Downloader = new WebClient();
Stream ImageStream = Downloader.OpenRead(URL);
Bitmap DownloadImage = Bitmap.FromStream(ImageStream) as Bitmap;
return DownloadImage;
}
catch (Exception)
{
return null;
}
}
어떻게 동작하나
WebClient.OpenRead는 URL에 요청을 보내고 응답 본문을 Stream으로 돌려줍니다. 이 스트림을 그대로 Bitmap.FromStream(실제로는 Image.FromStream)에 넘기면 GDI+가 PNG·JPG·GIF·BMP 같은 형식을 알아서 판별해 이미지 객체를 만들어 줍니다. FromStream의 반환형은 Image라서 as Bitmap으로 바꿔 돌려주는 구조입니다.
주소가 틀렸거나 서버가 404를 돌려주거나, 이미지가 아닌 HTML이 내려오면 예외가 나는데, 이 함수는 그런 경우를 전부 null로 돌려줍니다. 그래서 호출하는 쪽은 결과가 null인지만 보면 됩니다. 컴파일하려면 System.Net, System.IO, System.Drawing 네임스페이스를 using 해 두어야 합니다.
pictureBox1.Image = WebImageView("https://example.com/sample.png");
쓰면서 주의할 점
원본 코드는 WebClient와 Stream을 닫지 않습니다. 한두 번 부를 때는 티가 안 나지만, 수백 장을 반복해서 받으면 연결과 메모리가 정리되지 않고 쌓입니다. 다만 Image.FromStream으로 만든 이미지는 원래 스트림이 살아 있어야 한다는 제약이 있어서, 스트림을 바로 닫아 버리면 나중에 그림을 그리거나 저장할 때 "GDI+ 일반 오류"가 날 수 있습니다. 그래서 저는 스트림에서 읽은 이미지를 new Bitmap(...)으로 한 번 복사해서 돌려주고, 원래 스트림과 WebClient는 using으로 닫는 쪽을 씁니다. 복사본은 스트림과 연결이 끊어지니 안전하게 오래 쓸 수 있습니다.
스트림을 닫는 버전으로 고치면
위 이야기를 코드로 옮기면 아래처럼 됩니다. using 세 개가 끝나는 순간 WebClient, 응답 스트림, 원본 Image가 모두 정리되고, 돌려주는 건 그와 무관한 새 Bitmap입니다.
public static Bitmap LoadWebImage(string url)
{
using (var client = new WebClient())
using (var stream = client.OpenRead(url))
using (var img = Image.FromStream(stream))
{
return new Bitmap(img);
}
}
예외를 잡지 않았으니 실패하면 WebException이나 ArgumentException(이미지가 아닌 데이터일 때)이 호출한 쪽으로 올라옵니다. 원래 함수처럼 null을 받고 싶다면 바깥을 try로 감싸면 됩니다. 한 가지 차이가 있는데, new Bitmap(img)로 복사하면 애니메이션 GIF는 첫 프레임만 남습니다. 움직이는 GIF를 그대로 보여 줘야 한다면 복사하지 말고 스트림을 MemoryStream에 통째로 받아 둔 뒤 그 메모리 스트림을 이미지와 함께 살려 두는 방식을 써야 합니다.
PictureBox에 이미지를 계속 갈아 끼우는 화면이라면 이전 이미지도 정리해 주세요. pictureBox1.Image에 새 값을 넣기 전에 기존 이미지를 변수에 받아 두었다가 교체한 뒤 Dispose()를 부르면, 오래 켜 두는 프로그램에서 메모리가 계속 늘어나는 일을 줄일 수 있습니다.
catch (Exception)으로 모든 예외를 삼키는 것도 편하지만, 왜 실패했는지 알 수 없다는 단점이 있습니다. 디버깅 중에는 예외 메시지를 로그로 남겨 두면 원인을 훨씬 빨리 찾습니다. HTTPS 사이트에서 연결 자체가 안 되면 .NET Framework 버전에 따라 TLS 1.2 설정이 필요한 경우가 많으니 ServicePointManager.SecurityProtocol도 확인해 보세요.
UI 스레드에서 이 함수를 부르면 이미지를 받는 동안 폼이 멈춥니다. 큰 이미지나 여러 장을 받을 때는 Task.Run 등으로 백그라운드에서 받고, 결과만 UI 스레드에서 PictureBox에 넣는 게 좋습니다. 새로 짜는 코드라면 HttpClient.GetStreamAsync로 바꿔도 구조는 거의 같습니다.