Development note/C#

[C#] 이미지를 다루는 방법(이미지 포멧 변경, 이미지 합성, 이미지 태그 수정)

v명월v 2021. 1. 12. 17:03

안녕하세요. 명월입니다.


이 글은 C#에서 이미지를 다루는 방법(이미지 포멧 변경, 이미지 합성, 이미지 태그 수정)에 대한 글입니다.


예전에 C/C++ 시절에는 이미지를 다루려면 이미지 포멧을 알고 구조체를 알아야 화면에 그리거나 사용할 수 있었는데 C#은 이런 기본적인 구조체 등은 다 정의되어 있고 그냥 가져다 사용만 할 수 있다는 게 너무 편한 것 같습니다.

이러한 개발 생산성 때문에 C#을 계속 쓰는 지도 모르겠습니다.


프로그램의 영상 처리 공부를 하면 가장 많이 사용하는 레나 사진을 통해서 설명하겠습니다.

출처 - https://namu.wiki/w/레나 포르센


그리고 처음에 이미지를 다루는 이해를 돕기 위해서는 콘솔 환경보다는 윈도우 환경이 좋기 때문에 윈도우 폼를 만들어 보겠습니다.

using System;
using System.Windows.Forms;

namespace WindowForm
{
  // Form 클래스를 상속받는다.
  public class Program : Form
  {
    // 싱글 스레드 어트리뷰트
    [STAThread]
    // 실행 함수
    static void Main(string[] args)
    {
      // 콘솔 출력
      Console.WriteLine("Program start");
      // 환경에 맞는 스타일 설정
      Application.EnableVisualStyles();
      Application.SetCompatibleTextRenderingDefault(false);
      // 메시지 루프에 인스턴스를 생성한다.
      Application.Run(new Program());
    }
  }
}

저는 콘솔 환경에서 윈도우 폼을 만들었기 때문에 뒤에 콘솔 창도 실행이 됩니다.

이제 이미지를 다룰 준비는 끝났고 위 이미지를 읽어서 먼저 윈도우 폼에 그려보겠습니다.


먼저 이미지를 사용하기 위해서는 System.Drawing과 System.Drawing.Design을 추가합니다.

소스 작성을 합니다.

using System;
using System.Drawing;
using System.Windows.Forms;

namespace WindowForm
{
  // Form 클래스를 상속받는다.
  public class Program : Form
  {
    // Graphic 객체 - C/C++이라고 치면 DC(Device Context)와 비슷한 녀석
    private readonly Graphics graphics;
    // 이미지 객체
    private Image image;
    // 생성자
    public Program()
    {
      // Form으로 부터 Graphic 객체를 생성한다.
      graphics = this.CreateGraphics();
      // 이미지를 읽어 온다.
      image = Bitmap.FromFile(@"d:\work\lena.png");
    }
    // 윈도우 메시지 루프에 Paint 요청 때마다.
    protected override void OnPaint(PaintEventArgs e)
    {
      // 상위 클랫를 실행
      base.OnPaint(e);
      // 사각형 만들기 - 위치는 0,0 크기는 윈도우 사이즈 만큼
      var rect = new Rectangle(new Point(0, 0), this.Size);
      // 이미지를 그린다.
      graphics.DrawImage(image, rect);
    }

    // 싱글 스레드 어트리뷰트
    [STAThread]
    // 실행 함수
    static void Main(string[] args)
    {
      // 콘솔 출력
      Console.WriteLine("Program start");
      // 환경에 맞는 스타일 설정
      Application.EnableVisualStyles();
      Application.SetCompatibleTextRenderingDefault(false);
      // 메시지 루프에 인스턴스를 생성한다.
      Application.Run(new Program());
    }
  }
}

여기서 화면에 이미지가 깔끔하게 그려집니다.

위 소스에서는 이미지를 가져와서 Image 객체를 만들고 Graphic 객체(DC)를 이용해서 윈도우에 그렸습니다.


이미지는 여러가지 포멧이 있습니다. PNG 포멧이 있고 JPG, GIF, BMP 등등 여러가지 이미지 포멧이 있습니다만, 사실 이 포멧들은 데이터 구조이 완전 달라서 같은 이미지라도 포멧에 따라 용량의 차이가 생깁니다.

보통은 BMP 파일이 가장 크고 PNG, JPG 순으로 갑니다. 반드시 그런건 아닙니다만, BMP파일은 각 픽셀(Pixel)마다의 색의 bit값을 넣어서 표현한것이고 PNG와 JPG는 데이터 양자 기법과 치환 기법을 사용해서 비손실, 손실 압축 포멧입니다.

(예전에는 그 구분을 확실히 알았는데 요즘에는 객체가 다 구현이 되어 있으니 다 잊어버렸네요..)

결론은 파일 포멧이 다릅니다. 예전 C/C++ 같으면 최근에는 Boost나 내장 함수도 많이 발전해서 이제는 다 구현이 되어 있을 거라 생각되지만 제가 공부할 때까지만 해도 이 구조체 만들거나 인터넷에서 퍼와서 변환할 때 그에 맞는 변환 함수를 만들었어야 하는 기억이 있습니다.

그런데 C#은? .Net framework에 구현이 되어 있습니다. 그냥 명시만 해주면 끝입니다.

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;

namespace WindowForm
{
  // Form 클래스를 상속받는다.
  public class Program : Form
  {
    // Graphic 객체 - C/C++이라고 치면 DC(Device Context)와 비슷한 녀석
    private readonly Graphics graphics;
    // 이미지 객체
    private Image image;
    // 생성자
    public Program()
    {
      // Form으로 부터 Graphic 객체를 생성한다.
      graphics = this.CreateGraphics();
      // 이미지를 읽어 온다.
      image = Bitmap.FromFile(@"d:\work\lena.png");
      // png를 jpg파일로 변환
      image.Save(@"d:\work\lena.jpg", ImageFormat.Jpeg);
      // png를 bmp파일로 변환
      image.Save(@"d:\work\lena.bmp", ImageFormat.Bmp);
      // png를 ico파일로 변환
      image.Save(@"d:\work\lena.ico", ImageFormat.Icon);
    }
    // 윈도우 메시지 루프에 Paint 요청 때마다.
    protected override void OnPaint(PaintEventArgs e)
    {
      // 상위 클랫를 실행
      base.OnPaint(e);
      // 사각형 만들기 - 위치는 0,0 크기는 윈도우 사이즈 만큼
      var rect = new Rectangle(new Point(0, 0), this.Size);
      // 이미지를 그린다.
      graphics.DrawImage(image, rect);
    }

    // 싱글 스레드 어트리뷰트
    [STAThread]
    // 실행 함수
    static void Main(string[] args)
    {
      // 콘솔 출력
      Console.WriteLine("Program start");
      // 환경에 맞는 스타일 설정
      Application.EnableVisualStyles();
      Application.SetCompatibleTextRenderingDefault(false);
      // 메시지 루프에 인스턴스를 생성한다.
      Application.Run(new Program());
    }
  }
}

결과를 보시면 사이즈가 다 다른 이미지가 만들어 집니다. ICO 파일로도 변환이 되는 것보니 이제부터는 이미지 파일을 ICO로 변환해주는 사이트를 찾을 필요가 없을 것 같습니다.

C#에서 제공하는 이미지 포멧은 위와 같습니다. 제가 모르는 이미지 포멧도 있습니다. 이 정도면 개인적으로 이미지 포멧을 몰라도 변환하고 읽어 오는데는 문제 없을 것 같네요..


이미지 포멧 변경은 되었으니 이번에는 이미지를 조금 편집해 봅시다.

블로그를 운영하다보면 이미지에다가 저작권을 넣고 싶거나 워터 마크를 넣고 싶을 때가 있습니다. 글을 넣고 워터 마크를 넣어봅시다.

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;

namespace WindowForm
{
  // Form 클래스를 상속받는다.
  public class Program : Form
  {
    // Graphic 객체 - C/C++이라고 치면 DC(Device Context)와 비슷한 녀석
    private readonly Graphics graphics;
    // 이미지 객체
    private Image image;
    // 워터 마크 이미지 객체
    private Image watermark;
    // 이미지 옵션
    private ImageAttributes imageAttribute;
    // 생성자
    public Program()
    {
      // Form으로 부터 Graphic 객체를 생성한다.
      graphics = this.CreateGraphics();
      // 이미지를 읽어 온다.
      image = Bitmap.FromFile(@"d:\work\lena.png");
      // 워터 마크 이미지를 읽어 온다.
      watermark = Bitmap.FromFile(@"d:\work\tistory.png");
      // 컬러 매트릭스 인스턴스 생성
      ColorMatrix colormatrix = new ColorMatrix();
      // 투명도 설정
      colormatrix.Matrix33 = 0.4F;
      // 이미지 옵션 인스턴스 생성
      imageAttribute = new ImageAttributes();
      // 기본은 그대로 투명도만 설정한다.
      imageAttribute.SetColorMatrix(colormatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
    }
    // 윈도우 메시지 루프에 Paint 요청 때마다.
    protected override void OnPaint(PaintEventArgs e)
    {
      // 상위 클랫를 실행
      base.OnPaint(e);
      // 사각형 만들기 - 위치는 0,0 크기는 윈도우 사이즈 만큼
      var rect = new Rectangle(new Point(0, 0), new Size(this.Width, this.Height - 39));
      // 이미지를 그린다.
      graphics.DrawImage(image, rect);
      // 워터 마크 그리기 (이미지, DC 위치, 이미지 이동 x,y 이미지 늘리기 위한 크기 width, height, 유닛 타입, 옵션 설정)
      graphics.DrawImage(watermark, rect, 0, 0, watermark.Width, watermark.Height, GraphicsUnit.Pixel, imageAttribute);
      // 저작권 표시를 위한 글씨를 표시한다.
      graphics.DrawString("nowonbun.tistory.com", new Font(new FontFamily("Arial"), 20, FontStyle.Bold, GraphicsUnit.Pixel), Brushes.Black, new Point(0, this.Height - 60));
    }

    // 싱글 스레드 어트리뷰트
    [STAThread]
    // 실행 함수
    static void Main(string[] args)
    {
      // 콘솔 출력
      Console.WriteLine("Program start");
      // 환경에 맞는 스타일 설정
      Application.EnableVisualStyles();
      Application.SetCompatibleTextRenderingDefault(false);
      // 메시지 루프에 인스턴스를 생성한다.
      Application.Run(new Program());
    }
  }
}

윈도우 화면을 보면 윈도우 화면에 워터마크와 저작권 표시가 생겼습니다. 우리는 이걸 윈도우에 표시하는 게 주 목적이 아니기 때문에 파일로 출력을 합시다.

using System;
using System.Drawing;
using System.Drawing.Imaging;

namespace WindowForm
{
  public class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 이미지를 읽어온다.
      var image = Bitmap.FromFile(@"d:\work\lena.png");
      // 워터 마크 이미지를 읽어 온다.
      var watermark = Bitmap.FromFile(@"d:\work\tistory.png");
      // 컬러 매트릭스 인스턴스 생성
      ColorMatrix colormatrix = new ColorMatrix();
      // 투명도 설정
      colormatrix.Matrix33 = 0.4F;
      // 이미지 옵션 인스턴스 생성
      var imageAttribute = new ImageAttributes();
      // 기본은 그대로 투명도만 설정한다.
      imageAttribute.SetColorMatrix(colormatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
      // image로 부터 메모리 DC를 생성한다.
      var graphics = Graphics.FromImage(image);
      // 사각형 만들기 - 위치는 0,0 크기는 윈도우 사이즈 만큼
      var rect = new Rectangle(new Point(0, 0), new Size(image.Width, image.Height - 39));
      // 워터 마크 그리기 (이미지, DC 위치, 이미지 이동 x,y 이미지 늘리기 위한 크기 width, height, 유닛 타입, 옵션 설정)
      graphics.DrawImage(watermark, rect, 0, 0, watermark.Width, watermark.Height, GraphicsUnit.Pixel, imageAttribute);
      // 저작권 표시를 위한 글씨를 표시한다.
      graphics.DrawString("nowonbun.tistory.com", new Font(new FontFamily("Arial"), 20, FontStyle.Bold, GraphicsUnit.Pixel), Brushes.Black, new Point(0, image.Height - 30));
      // DC에 저장 -> image 객체로 이동(실제 물리 파일은 영향없음)
      graphics.Save();
      // 파일로 저장한다.
      image.Save(@"d:\work\lena2.png");
      // 콘솔 출력
      Console.WriteLine("Press any key...");
      Console.ReadKey();
    }
  }
}

Form에 관한 소스를 제거하고 image를 읽어오고 그리고 저장, 그리고 파일로 출력까지 합니다.

출력이 되어 표기가 되었습니다. 블로그에 글을 쓸 때, 이미지 자동 변환 툴을 만들면 참 편리하게 저작권 관리등이 될 것같네요..


이미지 자체를 수정하는 소스를 작성했으니 이번에는 이미지 태그를 수정해 보겠습니다.

이미지 포멧 별로 지원하는 태그가 존재합니다.

이미지 Property로 하면 각 이미지 포멧에 대한 태그 정보가 나옵니다.(죄송합니다. 제 OS가 일본 버젼이라 일본어로 표시되네요.. 양해해 주세요.)

이 태그 정보는 이미지 포멧마다 다릅니다만, 위 이미지는 JPG에 대한 포멧 정보입니다.


보통 이런 포멧 정보는 스마트 폰이나 카메라로 촬영을 하게 되면 기본적으로 작성이 됩니다. 그러나 우리는 이걸 수정하려고 합니다.

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Text;

namespace WindowForm
{
  public class Program
  {
    // 태그 번호
    enum PropertyTag : int
    {
      PropertyTagGpsVer = 0x0000,
      PropertyTagGpsLatitudeRef = 0x0001,
      PropertyTagGpsLatitude = 0x0002,
      PropertyTagGpsLongitudeRef = 0x0003,
      PropertyTagGpsLongitude = 0x0004,
      PropertyTagGpsAltitudeRef = 0x0005,
      PropertyTagGpsAltitude = 0x0006,
      PropertyTagGpsGpsTime = 0x0007,
      PropertyTagGpsGpsSatellites = 0x0008,
      PropertyTagGpsGpsStatus = 0x0009,
      PropertyTagGpsGpsMeasureMode = 0x000A,
      PropertyTagGpsGpsDop = 0x000B,
      PropertyTagGpsSpeedRef = 0x000C,
      PropertyTagGpsSpeed = 0x000D,
      PropertyTagGpsTrackRef = 0x000E,
      PropertyTagGpsTrack = 0x000F,
      PropertyTagGpsImgDirRef = 0x0010,
      PropertyTagGpsImgDir = 0x0011,
      PropertyTagGpsMapDatum = 0x0012,
      PropertyTagGpsDestLatRef = 0x0013,
      PropertyTagGpsDestLat = 0x0014,
      PropertyTagGpsDestLongRef = 0x0015,
      PropertyTagGpsDestLong = 0x0016,
      PropertyTagGpsDestBearRef = 0x0017,
      PropertyTagGpsDestBear = 0x0018,
      PropertyTagGpsDestDistRef = 0x0019,
      PropertyTagGpsDestDist = 0x001A,
      PropertyTagNewSubfileType = 0x00FE,
      PropertyTagSubfileType = 0x00FF,
      PropertyTagImageWidth = 0x0100,
      PropertyTagImageHeight = 0x0101,
      PropertyTagBitsPerSample = 0x0102,
      PropertyTagCompression = 0x0103,
      PropertyTagPhotometricInterp = 0x0106,
      PropertyTagThreshHolding = 0x0107,
      PropertyTagCellWidth = 0x0108,
      PropertyTagCellHeight = 0x0109,
      PropertyTagFillOrder = 0x010A,
      PropertyTagDocumentName = 0x010D,
      PropertyTagImageDescription = 0x010E,
      PropertyTagEquipMake = 0x010F,
      PropertyTagEquipModel = 0x0110,
      PropertyTagStripOffsets = 0x0111,
      PropertyTagOrientation = 0x0112,
      PropertyTagSamplesPerPixel = 0x0115,
      PropertyTagRowsPerStrip = 0x0116,
      PropertyTagStripBytesCount = 0x0117,
      PropertyTagMinSampleValue = 0x0118,
      PropertyTagMaxSampleValue = 0x0119,
      PropertyTagXResolution = 0x011A,
      PropertyTagYResolution = 0x011B,
      PropertyTagPlanarConfig = 0x011C,
      PropertyTagPageName = 0x011D,
      PropertyTagXPosition = 0x011E,
      PropertyTagYPosition = 0x011F,
      PropertyTagFreeOffset = 0x0120,
      PropertyTagFreeByteCounts = 0x0121,
      PropertyTagGrayResponseUnit = 0x0122,
      PropertyTagGrayResponseCurve = 0x0123,
      PropertyTagT4Option = 0x0124,
      PropertyTagT6Option = 0x0125,
      PropertyTagResolutionUnit = 0x0128,
      PropertyTagPageNumber = 0x0129,
      PropertyTagTransferFunction = 0x012D,
      PropertyTagSoftwareUsed = 0x0131,
      PropertyTagDateTime = 0x0132,
      PropertyTagArtist = 0x013B,
      PropertyTagHostComputer = 0x013C,
      PropertyTagPredictor = 0x013D,
      PropertyTagWhitePoint = 0x013E,
      PropertyTagPrimaryChromaticities = 0x013F,
      PropertyTagColorMap = 0x0140,
      PropertyTagHalftoneHints = 0x0141,
      PropertyTagTileWidth = 0x0142,
      PropertyTagTileLength = 0x0143,
      PropertyTagTileOffset = 0x0144,
      PropertyTagTileByteCounts = 0x0145,
      PropertyTagInkSet = 0x014C,
      PropertyTagInkNames = 0x014D,
      PropertyTagNumberOfInks = 0x014E,
      PropertyTagDotRange = 0x0150,
      PropertyTagTargetPrinter = 0x0151,
      PropertyTagExtraSamples = 0x0152,
      PropertyTagSampleFormat = 0x0153,
      PropertyTagSMinSampleValue = 0x0154,
      PropertyTagSMaxSampleValue = 0x0155,
      PropertyTagTransferRange = 0x0156,
      PropertyTagJPEGProc = 0x0200,
      PropertyTagJPEGInterFormat = 0x0201,
      PropertyTagJPEGInterLength = 0x0202,
      PropertyTagJPEGRestartInterval = 0x0203,
      PropertyTagJPEGLosslessPredictors = 0x0205,
      PropertyTagJPEGPointTransforms = 0x0206,
      PropertyTagJPEGQTables = 0x0207,
      PropertyTagJPEGDCTables = 0x0208,
      PropertyTagJPEGACTables = 0x0209,
      PropertyTagYCbCrCoefficients = 0x0211,
      PropertyTagYCbCrSubsampling = 0x0212,
      PropertyTagYCbCrPositioning = 0x0213,
      PropertyTagREFBlackWhite = 0x0214,
      PropertyTagGamma = 0x0301,
      PropertyTagICCProfileDescriptor = 0x0302,
      PropertyTagSRGBRenderingIntent = 0x0303,
      PropertyTagImageTitle = 0x0320,
      PropertyTagResolutionXUnit = 0x5001,
      PropertyTagResolutionYUnit = 0x5002,
      PropertyTagResolutionXLengthUnit = 0x5003,
      PropertyTagResolutionYLengthUnit = 0x5004,
      PropertyTagPrintFlags = 0x5005,
      PropertyTagPrintFlagsVersion = 0x5006,
      PropertyTagPrintFlagsCrop = 0x5007,
      PropertyTagPrintFlagsBleedWidth = 0x5008,
      PropertyTagPrintFlagsBleedWidthScale = 0x5009,
      PropertyTagHalftoneLPI = 0x500A,
      PropertyTagHalftoneLPIUnit = 0x500B,
      PropertyTagHalftoneDegree = 0x500C,
      PropertyTagHalftoneShape = 0x500D,
      PropertyTagHalftoneMisc = 0x500E,
      PropertyTagHalftoneScreen = 0x500F,
      PropertyTagJPEGQuality = 0x5010,
      PropertyTagGridSize = 0x5011,
      PropertyTagThumbnailFormat = 0x5012,
      PropertyTagThumbnailWidth = 0x5013,
      PropertyTagThumbnailHeight = 0x5014,
      PropertyTagThumbnailColorDepth = 0x5015,
      PropertyTagThumbnailPlanes = 0x5016,
      PropertyTagThumbnailRawBytes = 0x5017,
      PropertyTagThumbnailSize = 0x5018,
      PropertyTagThumbnailCompressedSize = 0x5019,
      PropertyTagColorTransferFunction = 0x501A,
      PropertyTagThumbnailData = 0x501B,
      PropertyTagThumbnailImageWidth = 0x5020,
      PropertyTagThumbnailImageHeight = 0x5021,
      PropertyTagThumbnailBitsPerSample = 0x5022,
      PropertyTagThumbnailCompression = 0x5023,
      PropertyTagThumbnailPhotometricInterp = 0x5024,
      PropertyTagThumbnailImageDescription = 0x5025,
      PropertyTagThumbnailEquipMake = 0x5026,
      PropertyTagThumbnailEquipModel = 0x5027,
      PropertyTagThumbnailStripOffsets = 0x5028,
      PropertyTagThumbnailOrientation = 0x5029,
      PropertyTagThumbnailSamplesPerPixel = 0x502A,
      PropertyTagThumbnailRowsPerStrip = 0x502B,
      PropertyTagThumbnailStripBytesCount = 0x502C,
      PropertyTagThumbnailResolutionX = 0x502D,
      PropertyTagThumbnailResolutionY = 0x502E,
      PropertyTagThumbnailPlanarConfig = 0x502F,
      PropertyTagThumbnailResolutionUnit = 0x5030,
      PropertyTagThumbnailTransferFunction = 0x5031,
      PropertyTagThumbnailSoftwareUsed = 0x5032,
      PropertyTagThumbnailDateTime = 0x5033,
      PropertyTagThumbnailArtist = 0x5034,
      PropertyTagThumbnailWhitePoint = 0x5035,
      PropertyTagThumbnailPrimaryChromaticities = 0x5036,
      PropertyTagThumbnailYCbCrCoefficients = 0x5037,
      PropertyTagThumbnailYCbCrSubsampling = 0x5038,
      PropertyTagThumbnailYCbCrPositioning = 0x5039,
      PropertyTagThumbnailRefBlackWhite = 0x503A,
      PropertyTagThumbnailCopyRight = 0x503B,
      PropertyTagLuminanceTable = 0x5090,
      PropertyTagChrominanceTable = 0x5091,
      PropertyTagFrameDelay = 0x5100,
      PropertyTagLoopCount = 0x5101,
      PropertyTagGlobalPalette = 0x5102,
      PropertyTagIndexBackground = 0x5103,
      PropertyTagIndexTransparent = 0x5104,
      PropertyTagPixelUnit = 0x5110,
      PropertyTagPixelPerUnitX = 0x5111,
      PropertyTagPixelPerUnitY = 0x5112,
      PropertyTagPaletteHistogram = 0x5113,
      PropertyTagCopyright = 0x8298,
      PropertyTagExifExposureTime = 0x829A,
      PropertyTagExifFNumber = 0x829D,
      PropertyTagExifIFD = 0x8769,
      PropertyTagICCProfile = 0x8773,
      PropertyTagExifExposureProg = 0x8822,
      PropertyTagExifSpectralSense = 0x8824,
      PropertyTagGpsIFD = 0x8825,
      PropertyTagExifISOSpeed = 0x8827,
      PropertyTagExifOECF = 0x8828,
      PropertyTagExifVer = 0x9000,
      PropertyTagExifDTOrig = 0x9003,
      PropertyTagExifDTDigitized = 0x9004,
      PropertyTagExifCompConfig = 0x9101,
      PropertyTagExifCompBPP = 0x9102,
      PropertyTagExifShutterSpeed = 0x9201,
      PropertyTagExifAperture = 0x9202,
      PropertyTagExifBrightness = 0x9203,
      PropertyTagExifExposureBias = 0x9204,
      PropertyTagExifMaxAperture = 0x9205,
      PropertyTagExifSubjectDist = 0x9206,
      PropertyTagExifMeteringMode = 0x9207,
      PropertyTagExifLightSource = 0x9208,
      PropertyTagExifFlash = 0x9209,
      PropertyTagExifFocalLength = 0x920A,
      PropertyTagExifMakerNote = 0x927C,
      PropertyTagExifUserComment = 0x9286,
      PropertyTagExifDTSubsec = 0x9290,
      PropertyTagExifDTOrigSS = 0x9291,
      PropertyTagExifDTDigSS = 0x9292,
      PropertyTagExifFPXVer = 0xA000,
      PropertyTagExifColorSpace = 0xA001,
      PropertyTagExifPixXDim = 0xA002,
      PropertyTagExifPixYDim = 0xA003,
      PropertyTagExifRelatedWav = 0xA004,
      PropertyTagExifInterop = 0xA005,
      PropertyTagExifFlashEnergy = 0xA20B,
      PropertyTagExifSpatialFR = 0xA20C,
      PropertyTagExifFocalXRes = 0xA20E,
      PropertyTagExifFocalYRes = 0xA20F,
      PropertyTagExifFocalResUnit = 0xA210,
      PropertyTagExifSubjectLoc = 0xA214,
      PropertyTagExifExposureIndex = 0xA215,
      PropertyTagExifSensingMethod = 0xA217,
      PropertyTagExifFileSource = 0xA300,
      PropertyTagExifSceneType = 0xA301,
      PropertyTagExifCfaPattern = 0xA302
    }
    // 이미지 타입 번호
    static dynamic TranData(short type, byte[] value)
    {
      switch (type)
      {
        case 1:
          return value;
        case 2:
          return Encoding.ASCII.GetString(value);
        case 3:
          return BitConverter.ToInt16(value, 0);
        case 4:
        case 5:
          return BitConverter.ToInt32(value, 0);
        case 10:
          return BitConverter.ToInt64(value, 0);
      }
      return null;
    }
    // 실행 함수
    static void Main(string[] args)
    {
      // 이미지를 읽어온다.
      var image = Image.FromFile(@"d:\work\lena.jpg");
      // 프로퍼티를 읽어 온다.
      foreach (var property in image.PropertyItems)
      {
        // 타입별로
        var tag = (PropertyTag)property.Id;
        // 콘솔 출력
        Console.WriteLine($"{tag.ToString()} - {TranData(property.Type, property.Value)}");
      }

      // 콘솔 출력
      Console.WriteLine("Press any key...");
      Console.ReadKey();
    }
  }
}

이미지를 읽어와서 태그 정보(EXIF)를 읽어 왔습니다. 잘 읽어 오는 것 같습니다.

참고로 태그 번호와 이미지 타입 번호는 MSDN을 참고 했습니다.

링크 - PropertyItem.Id Property

링크 - PropertyItem.Type Property


읽어 왔으니 이번에는 태그 정보를 넣어 보겠습니다.

using System;
using System.Drawing;
using System.Text;

namespace WindowForm
{
  public class Program
  {
    // 태그 번호
    enum PropertyTag : int
    {
      PropertyTagArtist = 0x013B
    }
    // 실행 함수
    static void Main(string[] args)
    {
      // 이미지를 읽어온다.
      var image = Image.FromFile(@"d:\work\lena.jpg");
      // 0번쨰 PropertyItem을 취득.. 왜인지 PropertyItem클래스는 인스턴스 생성이 되지 않는다.
      var item = image.PropertyItems[0];
      // 작성자
      item.Id = (int)PropertyTag.PropertyTagArtist;
      // ASCII 타입으로 설정
      item.Type = 2;
      // nowonbun을 ASCII byte배열로 변경
      item.Value = Encoding.ASCII.GetBytes("nowonbun\0");
      // 길이 넣기
      item.Len = item.Value.Length;
      // Property 세팅
      image.SetPropertyItem(item);
      // 저장
      image.Save(@"d:\work\lena3.jpg");

      // 콘솔 출력
      Console.WriteLine("Press any key...");
      Console.ReadKey();
    }
  }
}

에러 없이 실행되었습니다. 이미지 태그 정보를 확인해 봅시다.

태그 정보가 추가되었는 것을 확인할 수 있습니다.


생각보다 이미지 태그 번호가 엄청나게 많네요.. 하나하나 다 설명하고 싶지만 너무 많아서 여기서 설명하기가 힘들꺼 같네요.. 필요하신 분들은 직접 디버깅해보면서 태그 데이터를 넣어보며 확인하는 수밖에 없을 것 같습니다.


여기까지 C#에서 이미지를 다루는 방법(이미지 포멧 변경, 이미지 합성, 이미지 태그 수정)에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.