본문 바로가기

Develop/MAUI 가이드

[Xamarin] 픽셀 및 기기 독립 Unit (Device-Independent Units)

반응형

Device-Independent Units
Device-Independent Units

픽셀 및 기기 독립 Unit

Xamarin 은 크로스 플랫폼, 더 나아가 모든 플랫폼을 지원하려는 야망을 가진 프레임워크입니다. 따라서 기기마다 다른 Unit 기준을 충족시켜야만 합니다. 개발자에게는 이게 무슨 소린지 감이 잡히질 않습니다. 왜냐하면 개발자는 디지털 단위인 픽셀 에 익숙해져 있기 때문입니다. 개발자에게 화면이란 픽셀로 이루어져 있는 장치이며, 픽셀만 맞추면 화면에 잘 표시될 것 같은 헛된 믿음을 가집니다. 하지만 디자이너에게 중요한건 실제 화면에 표현되는 물리적인 비율이 중요합니다. 픽셀만 맞춰서 개발을 하게 되면 화면에 따라 크기가 달라 보입니다! 특히 스마트폰의 화면 크기는 모두 제각각입니다. 심지어 화면마다 표현할 수 있는 픽셀 수도 기기마다 다릅니다. 간단한 실험으로 픽셀을 믿을 수 없는 이유를 보여드리겠습니다.

만약 A 스마트폰 화면의 크기가 1inch x 1inch 이고 해상도가 100px x 100px 이라 해봅시다. 만약 픽셀을 기준으로 코드를 짜서 전체 화면을 채우려면 100px x 100px 이미지를 그리면 됩니다.

하지만 만약 B 스마트폰 화면의 크기가 A 스마트폰과 같이 1inch x 1inch 인데 해상도가 200px x 200px 이라면, A 스마트폰 전체 화면에 맞도록 만든 UI 가 B 스마트폰에서는 반절 밖에 채우질 못하게 됩니다.

이처럼 개발자는 잘 모르지만 픽셀이라는 단위는 어느 기기인지에 따라 실제 사용자가 경험하는 크기가 달라지게 되는 문제가 있습니다.

Xamarin 은 Device-Independent Units (DIUs) 라는 단위를 사용해 이러한 문제점을 해결합니다. DIUs 는 unit 으로 적으며 아래 사항을 기억하셔야 합니다.

1 inch = 160 units

즉 Xamarin 에서 160 units 으로 선을 그리면 어느 화면에서든 동일하게 1 inch 크기로 나온다는 의미입니다. 그러면 이게 어떻게 가능한지 보여드리도록 하겠습니다.

먼저 PPI (Pixel Per Inch) 라는 개념을 설명드리도록 하겠습니다. PPI 란 1 inch 에 몇 px 이 들어가는지를 나타내는 개념입니다. A 스마트폰의 PPI는 100 이고, B 스마트폰의 PPI는 200 입니다. 따라서 아래와 같이 스마트폰의 PPI 에 따라 1 unitpx 단위로 얼마인지 계산할 수 있습니다.

  • A 스마트폰에서 160 unit = 1 inch = 100 px , 1 unit = (160 unit / 100 PPI) = 1.6 px
  • B 스마트폰에서 160 unit = 1 inch = 200 px , 1 unit = (160 unit / 200 PPI) = 0.8 px

여기서 역으로 A 스마트폰을 꽉 채우는 크기의 unit 를 구하면,

100 px * 1.6 = 160 unit

B 스마트폰을 꽉 채우는 크기의 unit 은 A 스마트폰과 동일한 unit 이 나오게 됩니다.

200 px * 0.8 = 160 unit

따라서 Z 스마트폰이 1inch x 1inch 크기일 때 K px x K px 이라도 160 unit 이라는 단위로 화면 전체를 채울 수 있습니다.

SkiaSharp

Xamarin 에서 재활용 가능한 View 나 애니메이션 효과를 만들 때 자주 사용하는게 SkiaSharp 라이브러리입니다. 여기서 SkiaSharp 가 등장하는 이유는 Skia 라이브러리가 픽셀 기반의 C++ 코드로 작성되었기 때문에 슬프게도 SkiaSharp 는 내부적으로 픽셀 단위를 기준으로 사용해서, Xamarin 과 함께 사용하게 되면 위에 스마트폰 화면으로 설명드린 것처럼 스마트폰마다 크기가 달라져 보이는 괴현상을 마주치게 됩니다. 저는 이를 단순하게 해결하기 위해 아래 코드를 사용하고 있습니다.

 /// <summary>
 /// Xamarin은 기본 단위로 unit을 사용하는 반면, Skia는 Pixel을 기본단위로 사용하므로 서로 변환해줘야 합니다.
 /// </summary>
 static class PixelUnitConverter
 {
     private const float UnitPerInch = 160.0f;

     public static float Density = Convert.ToSingle(DeviceDisplay.MainDisplayInfo.Density);
     public static float PixelPerInch 
     { 
         get => Convert.ToSingle(UnitPerInch * Density);
     }

     public static float GetRatio(float pixel, float unit)
     {
         return pixel / GetPixelFromUnit(unit);
     }

     public static float GetPixelFromUnit(float unit)
     {
         return unit * Density;
     }

     public static float GetPixelFromUnit(double unit)
     {
         return GetPixelFromUnit(Convert.ToSingle(unit));
     }

     public static float GetUnitFromPixel(float pixel)
     {
         return pixel / Density;
     }

     public static System.Drawing.RectangleF GetPixelRectFromUnitRect(Xamarin.Forms.Rectangle rectangle)
     {
         return new System.Drawing.RectangleF()
         {
             X = GetPixelFromUnit(rectangle.X),
             Y = GetPixelFromUnit(rectangle.Y),
             Width = GetPixelFromUnit(rectangle.Width),
             Height = GetPixelFromUnit(rectangle.Height)
         };
     }

     public static Xamarin.Forms.Rectangle GetUnitRectFromPixelRect(System.Drawing.RectangleF rectangle)
     {
         return new Xamarin.Forms.Rectangle()
         {
             X = GetUnitFromPixel(rectangle.X),
             Y = GetUnitFromPixel(rectangle.Y),
             Width = GetUnitFromPixel(rectangle.Width),
             Height = GetUnitFromPixel(rectangle.Height)
         };
     }
 }
반응형