먼저 글을 읽어주어 감사함다...
합성한 이미지의 해상도를 구하고 그 해상도를 변경할 수 있는 방법을
알려주시면 감사하겠습니다..
포토샵에서는 그림의 해상도를 마음대로 조절할 수 있는데...
델파이에서 그런 기능을 구현할 수 없는지요??
참고로 이미지 컴포넌트에 있는 그림은 BMP입니다..
그림의 형태를 그대로 유지하면서 해상도를 바꾸어서 파일로 저장하거나..
복사기처럼 101%확대 혹은 99%로 축소하여 파일로 저장할 수 있는 방법이
있으면 알려주십시요.....
끝까지 읽어주어 감사함다...
> 먼저 글을 읽어주어 감사함다...
>
> 합성한 이미지의 해상도를 구하고 그 해상도를 변경할 수 있는 방법을
> 알려주시면 감사하겠습니다..
> 포토샵에서는 그림의 해상도를 마음대로 조절할 수 있는데...
> 델파이에서 그런 기능을 구현할 수 없는지요??
> 참고로 이미지 컴포넌트에 있는 그림은 BMP입니다..
>
> 그림의 형태를 그대로 유지하면서 해상도를 바꾸어서 파일로 저장하거나..
> 복사기처럼 101%확대 혹은 99%로 축소하여 파일로 저장할 수 있는 방법이
> 있으면 알려주십시요.....
>
> 끝까지 읽어주어 감사함다...
제일루 간단한 방법은...
컴포넌트 사서 쓰셔요~ (이미지 라이브러리) 라구 하는 컴포넌트 쓰셔요...
가격이 비싸서 그렇지... 진짜루 지금 이야기 하신거 말구두 무궁무진하게 많은 기능을 할수도 있답니다.
그런데.. 못산다면 어쩔수 없는 노력이 필요하겠지요 ^^
========우선 해상도 구하는 것에 대해서 이야기를 하자믄.....======
TotalNumBitsPerPixel := GetDeviceCaps(Canvas.Handle, BITSPIXEL) *
GetDeviceCaps(Canvas.Handle, PLANES);
해서 얻어오는 값이..
1 = 2 colors (monochrome)
4 = 16 colors
8 = 256 colors
15 = 32,768 colors
16 = 65,536 colors
24 = 16,777,216 colors
이렇게 되는 거랍니다. ^^
======== 다음 해상도를 변경하는 방법이라구 되어 있는데.. =====
위에서 쓴것처럼 간단한 것이 있을텐데.. 음냐.. 거까진 모르것구..
256칼라루 만드는 것 한다면..
만든이가 grahame.s.marsh@corp.courtaulds.co.uk 이사람인데..
난 절대루 모르는 사람입니다. ^^
unit Comp2;
{$IFNDEF WIN32}
Sorry, WIN 32 only!
{$ENDIF}
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ExtDlgs, ExtCtrls, StdCtrls, Buttons, ComCtrls;
// declare own bitmap file record, specifically for 256 colour bitmaps
type
T256Palette = array [0..255] of TRGBQuad;
P256Bitmap = ^T256Bitmap;
T256Bitmap = packed record
b256File : TBitmapFileHeader;
b256Info : TBitmapInfoHeader;
b256Pal : T256Palette;
b256Data : record end;
end;
type
TBitmapCompForm = class(TForm)
GroupBox1: TGroupBox;
InBrowseBtn: TBitBtn;
InFilenameEdit: TEdit;
InFilesizeLabel: TLabel;
InScrollBox: TScrollBox;
InImage: TImage;
OpenPictureDialog: TOpenPictureDialog;
GroupBox2: TGroupBox;
OutFilenameEdit: TEdit;
OutBrowseBtn: TBitBtn;
SaveDialog: TSaveDialog;
OutScrollBox: TScrollBox;
OutImage: TImage;
CompressBtn: TBitBtn;
OutFilesizeLabel: TLabel;
CompUsingLabel: TLabel;
PaletteCheckBox: TCheckBox;
QualityTrackBar: TTrackBar;
QualityLabel: TLabel;
Label1: TLabel;
procedure InBrowseBtnClick(Sender: TObject);
procedure QualityTrackBarChange(Sender: TObject);
procedure OutBrowseBtnClick(Sender: TObject);
procedure OutFilenameEditChange(Sender: TObject);
procedure CompressBtnClick(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
InBitmap : P256Bitmap; // copy of bitmap file
InSize, // copy of filesize
InDataSize, // size of bitmap data
InColours : integer; // number of colours
procedure FreeStuff;
public
end;
var
BitmapCompForm: TBitmapCompForm;
implementation
{$R *.DFM}
//-- calls to video for windopws dll -------------------------------------------
type
PICInfo = ^TICInfo;
TICInfo = packed record
dwSize, // sizeof (TICInfo)
fccType, // compressor type eg vidc
fccHandler, // compressor subtype eg rle
dwFlags, // lo word is type specific
dwVersion, // version of driver
dwVersionICM : DWORD; // version of the ICM
szName : array [0..15] of wchar; // short name
szDescription : array [0..127] of wchar; // long name
szDriver : array [0..127] of wchar; // driver that contains the compressor
end;
const
ICMODE_COMPRESS = 1;
ICTYPE_VIDEO = ord ('v') +
ord ('i') shl 8 +
ord ('d') shl 16 +
ord ('c') shl 24;
type
TICHandle = THandle;
function ICLocate (fccType, fccHandler: DWORD; lpbiIn, lpbmOut : PBitmapInfoHeader; wFlags: word) : TICHandle;
stdcall; external 'msvfw32.dll' name 'ICLocate';
function ICGetInfo (Handle: TICHandle; var ICInfo: TICInfo; cb: DWORD): LRESULT;
stdcall; external 'msvfw32.dll' name 'ICGetInfo';
function ICImageCompress (Handle: TICHandle; uiFlags: UINT; lpbiIn: PBitmapInfo;
lpBits: pointer; lpbiOut: PBitmapInfo; lQuality: integer; plSize: PInteger): HBitmap;
stdcall; external 'msvfw32.dll' name 'ICImageCompress';
function ICClose (Handle: TICHandle): LRESULT;
stdcall; external 'msvfw32.dll' name 'ICClose';
//--- compressor form ----------------------------------------------------------
const
FSStr = 'File size: %d';
CUStr = 'Compressed using: %s';
BitmapSignature = $4D42;
procedure TBitmapCompForm.FormDestroy(Sender: TObject);
begin
FreeStuff
end;
procedure TBitmapCompForm.FreeStuff;
begin
if InSize <> 0 then
begin
FreeMem (InBitmap, InSize);
InBitmap := nil;
InSize := 0
end
end;
procedure TBitmapCompForm.InBrowseBtnClick(Sender: TObject);
var
Bitmap : TBitmap;
begin
with OpenPictureDialog do
if Execute then
begin
InFilesizeLabel.Caption := Format (FSStr, [0]);
InImage.Picture := nil;
InFilenameEdit.Text := '';
FreeStuff;
with TFileStream.Create (Filename, fmOpenRead) do
try
InSize := Size;
GetMem (InBitmap, InSize);
Read (InBitmap^, InSize);
with InBitmap^ do
if b256File.bfType = BitmapSignature then
if b256Info.biBitCount = 8 then
if b256Info.biCompression = BI_RGB then
begin
// Ok, we have a 256 colour, uncompressed bitmap
InFilenameEdit.Text := Filename;
// determine number of entries in palette
if b256Info.biClrUsed = 0 then
InColours := 256
else
InColours := b256Info.biClrUsed;
// determine size of data bits
with InBitmap^.b256Info do
if biSizeImage = 0 then
InDataSize := biWidth * biHeight
else
InDataSize := biSizeImage
end else
ShowMessage ('Bitmap already compressed')
else
ShowMessage ('Not a 256 colour bitmap')
else
ShowMessage ('Not a bitmap')
finally
Free
end;
// show the bitmap and file size
if InFileNameEdit.Text <> '' then
begin
Bitmap := TBitmap.Create;
try
Bitmap.LoadFromFile (InFilenameEdit.Text);
InImage.Picture.Bitmap := Bitmap
finally
Bitmap.Free
end;
InScrollBox.VertScrollBar.Range := InBitmap^.b256Info.biHeight;
InScrollBox.HorzScrollBar.Range := InBitmap^.b256Info.biWidth;
InFilesizeLabel.Caption := Format (FSStr, [InBitmap^.b256File.bfSize])
end
end
end;
procedure TBitmapCompForm.OutBrowseBtnClick(Sender: TObject);
begin
with SaveDialog do
if Execute then
OutFilenameEdit.Text := Filename
end;
//--- Palette Compression ------------------------------------------------------
// compress a 256 colour palette by removing unused entries
// returns new number of entries
function CompressPalette (var Pal: T256Palette; Data: pointer; DataSize: integer): word;
type
TPaletteUsed = packed record
Used : boolean;
NewEntry : byte;
end;
TPaletteUsedArray = array [0..255] of TPaletteUsed;
var
PUArray: TPaletteUsedArray;
Scan: PByte;
NewValue,
Loop: integer;
NewPal : T256Palette;
begin
// look through the bitmap data bytes looking for palette entries in use
fillchar (PUArray, sizeof (PUArray), 0);
Scan:= Data;
for Loop:= 1 to DataSize do
begin
PUArray[Scan^].Used := true;
inc (Scan)
end;
// go through palette and set new entry numbers for those in use
NewValue := 0;
for Loop:= 0 to 255 do
with PUArray[Loop] do
if Used then
begin
NewEntry := NewValue;
inc (NewValue);
end;
Result := NewValue; // return number in use
if NewValue = 256 then
exit; // QED
// go through bitmap data assigninging new palette numbers
Scan:= Data;
for Loop:= 1 to DataSize do
begin
Scan^ := PUArray[Scan^].NewEntry;
inc (Scan)
end;
// create a new palette and copy across only those entries in use
fillchar (NewPal, sizeof (T256Palette), 0);
for Loop := 0 to 255 do
with PUArray [Loop] do
if Used then
NewPal[NewEntry] := Pal [Loop];
// return the new palette
Pal := NewPal
end;
//--- try to compress input image -> output image ------------------------------
procedure TBitmapCompForm.CompressBtnClick(Sender: TObject);
var
Bitmap: TBitmap;
Handle: THandle;
CompressHandle: integer;
ICInfo: TICInfo;
OutBitmap,
InBitmapCopy : P256Bitmap;
CompressedStuff,
OutData,
InDataCopy : pointer;
OutSize,
OutColours : integer;
begin
// make an output bitmap file
GetMem (OutBitmap, sizeof (T256Bitmap));
try
// make a copy of the input file as we will play with the data
GetMem (InBitmapCopy, InSize);
try
Move (InBitmap^, InBitmapCopy^, InSize);
InDataCopy := pointer (integer(InBitmapCopy) + sizeof (TBitmapFileHeader) +
sizeof (TBitmapInfoHeader) + InColours * sizeof (TRGBQuad));
// crunch the palette
with InBitmapCopy^ do
if PaletteCheckBox.Checked then
OutColours := CompressPalette (b256Pal, InDataCopy, InDataSize)
else
OutColours := InColours;
// now copy the input file to fill in most of the output bitmap values
Move (InBitmapCopy^, OutBitmap^, sizeof (T256Bitmap));
// set the compression required
OutBitmap^.b256Info.biCompression := BI_RLE8;
// find a compressor
CompressHandle := ICLocate (ICTYPE_VIDEO, 0, @InBitmapCopy^.b256Info,
@OutBitmap.b256Info, ICMODE_COMPRESS);
try
fillchar (ICInfo, sizeof (TICInfo), 0);
ICInfo.dwSize := sizeof (TICInfo);
// get info on the compressor
ICGetInfo (CompressHandle, ICInfo, sizeof (TICInfo));
OutSize := 0; // best compression
// now compress the image
Handle := ICImageCompress (CompressHandle, 0, @InBitmapCopy^.b256Info,
InDataCopy, @OutBitmap^.b256Info, QualityTrackBar.Position*100, @OutSize);
finally
ICClose (CompressHandle)
end;
if Handle <> 0 then
begin
// get the compressed data
CompressedStuff := GlobalLock (Handle);
try
// modify the filesize and offset in case palette has shrunk
with OutBitmap^.b256File do
begin
bfOffBits := sizeof (TBitmapFileHeader) + sizeof(TBitmapInfoHeader) +
OutColours * sizeof (TRGBQuad);
bfSize := bfOffBits + OutSize
end;
// locate the data
OutData := pointer (integer(CompressedStuff) +
sizeof(TBitmapInfoHeader) + InColours * sizeof (TRGBQuad));
// modify the bitmap info header
with OutBitmap^.b256Info do
begin
biSizeImage := OutSize;
biClrUsed := OutColours;
biClrImportant := 0
end;
// save the bitmap to disc
with TFileStream.Create (OutFilenameEdit.Text, fmCreate) do
try
write (OutBitmap^, sizeof (TBitmapFileHeader) + sizeof (TBitmapInfoHeader));
write (InBitmapCopy^.b256Pal, OutColours*sizeof (TRGBQuad));
write (OutData^, OutSize)
finally
Free
end;
// view the result
Bitmap := TBitmap.Create;
try
Bitmap.LoadFromFile (OutFilenameEdit.Text);
OutImage.Picture.Bitmap := Bitmap
finally
Bitmap.Free
end;
// set the scrollbars and give some stats
with OutBitmap^ do
begin
OutScrollBox.VertScrollBar.Range := b256Info.biHeight;
OutScrollBox.HorzScrollBar.Range := b256Info.biWidth;
OutFileSizeLabel.Caption := Format (FSStr, [b256File.bfSize]);
CompUsingLabel.Caption := Format (CUStr, [WideCharToString (ICInfo.szDescription)])
end
// now tidy up
finally
GlobalUnlock (Handle)
end
end else
ShowMessage ('Bitmap could not be compressed')
finally
FreeMem (InBitmapCopy, InSize)
end
finally
FreeMem (OutBitmap, sizeof (T256Bitmap))
end
end;
procedure TBitmapCompForm.QualityTrackBarChange(Sender: TObject);
begin
QualityLabel.Caption := IntToStr (QualityTrackBar.Position)
end;
procedure TBitmapCompForm.OutFilenameEditChange(Sender: TObject);
begin
CompressBtn.Enabled := (InFilenameEdit.Text <> '') and
(OutFilenameEdit.Text <> '')
end;
end.
===== 그다음은 확대 및 축소 인감요?? =============
델파이는 이미지 크기를 변경하는 쉬운 방법 두가지를 제공합니다.
Image 컴포넌트를 사용한다면, 속성중에 Stretch라는 것이 있지요? 이것은 AutoSize와 반대되는 역할을 한다는 것은 알고 계시죠? 즉, 컴포넌트 크기에 맞춰서 사용자가 지정한 이미지를 늘리거나 축소시키거나 하는 것이지요.
또하나의 방법은, TCanvas 객체에 있는 StretchDraw라는 프로시져를 사용하는 것입니다. 이것은 지정한 그래픽의 크기를 목적 객체의 크기에 맞춘후 복사를 하는 것입니다.
아래 코드를 보면 imgInput 콘트롤의 그림을 imgOutput 콘트롤에 맞추어 넣고 있습니다.
imgOutput.Canvas.StretchDraw(imgOutput.ClientRect, imgInput.Picture.Graphic);
이 방법은 이미지를 늘릴경우, 배수에 따라서 똑같은 이미지를 그 배수만큼 반복시켜서 전체 이미지를 확대 시키고, 축소시에는 이미지중 배수에 따라 중간 중간의 픽셀을 제거하는 방식으로 하기 때문에 속도가 빠르고, 사용하기도 쉽습니다. 하지만, 이러한 확대시에는 그림 모양이 각지게 보이는 현상이 있으며, 축소시에는 필요한 정보가 제거되어 그림에서 보이지 않게 될 수도 있습니다.
여기에서는 이러한 단순한 방법대신에 약간의 공식을 사용해서 좀더 부드럽게 이미지가 보일 수 있도록 하는 알고리즘을 설명드릴 것입니다.
이미지를 줄이기 위해서 TCanvas.StretchDraw를 사용하면 원본 이미지로부터 몇몇 픽셀들을 제거하게 됩니다. 이 방법의 문제점은 제거된 픽셀에 있던 정보들은 완전히 잃어버리게 된다는 것입니다. 만약 거기에 중요한 정보(수직선이 있는 픽셀들과 같이)가 있었다면 결과물에서는 보이지 않게 될 것입니다.
더 좋은 방법은 축소된 출력 이미지안에 들어갈 픽셀을 만들기 위해 근처의 픽셀들을 가지고 평균을 내는 것입니다. 이렇게 해서 출력 이미지의 각 픽셀에 대해 작업하는 것입니다. 각각의 출력 픽셀에 대하여, 프로그램이 출력 픽셀로 매핑될 입력 이미지의 픽셀들을 계산하는 것입니다. 그리고 나선, 이 입력 픽셀들의 Red, Green, Blue 값들에 대해 평균을 만들어 내는 것입니다.
델파이 자료실에 있는 리스트를 보시면 ShrinkPicture라는 프로시져를 발견하실 수 있을 겁니다. 이것은 이미지를 줄이는 루틴입니다. 이 함수는 입력 캔버스인 from_canvas의 영역인 from_x1 <= x <= from_x2 와 from_y1 <= y <= from_y2 를 출력 캔버스 to_canvas 의 영역 to_x1 <= x <= to_x2, to_y1 <= y <= to_y2 로 축소시킵니다.
여기에서 가장 중요한 코드는 입력 픽셀들로부터 출력 픽셀로의 값을 결정해서 매핑시키는 함수입니다. 이미지가 x와 y 방향으로 각각 xscale, yscale 만큼이 비율로 줄어든다면, 출력 픽셀 (to_x, to_y)는 아래와 같은 식에 따라 사각형 x1 <= x <= x2, y1 <= y <= y2 로 매핑됩니다.
y1 = Trunc((to_y - to_y1) / yscale + from_y1)
y2 = Trunc((to_y + 1 - to_y1) / yscale + from_y1) - 1
x1 = Trunc((to_x - to_x1) / xscale + from_x1)
x2 = Trunc((to_x + 1 - to_x1) / xscale + from_x1) - 1
프로시져가 입력 이미지의 사각형 부분을 찾아 낸후, 이 픽셀들의 Reg, Gree, Blue 색깔의 각각에 대해 평균을 찾아낸 후 출력 픽셀 (to_x, to_y)에 그 값을 지정하는 것입니다.
별로 어렵지 않죠? 이렇게 하면, 적어도 정보의 손실은 막을 수 있어서 축소된 이미지를 보면 어느정도 부드럽게 축소가 되었다는 것을 알 수 가 있습니다.
이미지를 늘리기 위해서 StretchDraw는 원본 이미지의 각 픽셀을 반복해서 늘리는 방식을 사용합니다. 이 알고리즘이 속도는 빠르지만, 이미지에 계단 현상이 보이게 됩니다. 이러한 문제를 해결하기 위한 알고리즘도 이미지 축소에서 쓰이는 방식과 비슷한 방식을 사용합니다.
이미지 확대를 위해 각각의 출력 픽셀에 대해서, 프로그램은 입력 이미지의 어느 부분이 출력 픽셀로 매핑되는지를 계산하게 됩니다. 출력 이미지가 더 크기 때문에, 하나의 출력 픽셀이 하나의 입력 픽셀에 정확히 매칭되지는 않을 것입니다. 입력 픽셀을 분할해서 사용한다고 하는 것이 많을 것입니다. 하지만, 하나의 픽셀을 나눌 수 는 없겠죠??? 그래서 아래와 같이 계산을 하게 되는 것입니다.
만약 이미지가 xscale, yscale의 비율로 x, y 방향에서 확대되었다면, 출력 픽셀 (to_x, to_y)는 아래 공식에 따라 점(sfrom_x, sfrom_y) 로부터 매핑됩니다.
sfrom_y = (to_y - to_y1) / yscale + from_y1
sfrom_x = (to_x - to_x1) / xscale + from_x1
그리고 나서, 프로그램은 계산된 입력 점 근처의 네개의 픽셀에 대해 평가합니다. 위의 그림에서, 이 픽셀들은 왼쪽 상단 구석의 네개에 해당합니다. 이 픽셀들은 각각
(ifrom_x, ifrom_y)
(ifrom_x + 1, ifrom_y)
(ifrom_x, ifrom_y + 1)
(ifrom_x + 1, ifrom_y + 1)
이며 이때의 ifrom_x, ifrom_y의 값은 아래와 같습니다.
ifrom_y = Trunc(sfrom_y)
ifrom_x = Trunc(sfrom_x)
프로그램은 이 네개의 픽셀에 대한 Red, Green, Blue 칼라 값을 계산해 냅니다. 이것은 출력 픽셀에 대해 가중치 평균(Weighted Average)을 사용하며, 이 평균은 입력 픽셀이 결과치에 근접하게 반영하도록 하기 위한 것입니다.
지금까지와는 달리, 여기에서는 출력 픽셀을 입력 이미지로 매핑시키게 됩니다. 이것을 사용해 이미지의 회전을 부드럽게 할 수 있습니다.
원점 (0, 0)을 기준으로 점(x, y)를 Theta 각도만큼 회전시키려 한다면, 결과값(x', y')은 다음과 같습니다.
x' = x * cos(theta) + y * sin(theta)
y' = -x * sin(theta) + y * cos(theta)
theta에 대해 반대방향으로 회전 할 경우의 각도는 -theta입니다. 즉, 출력 픽셀을 입력 위치로 매핑시키기 위해서는 회전 된 반대 방향으로 같은 각도만큼 돌리면 될 것이며, 이렇게 하기 위해서는 -theta를 사용하면 됩니다. 만약, 출력 픽셀이 (x', y')이라면 그에 대한 입력 픽셀(x, y)는 다음과 같습니다.
x = x' * Cos(-theta) + y' * Sin(-theta)
y = -x' * Sin(-theta) + y' * Cos(-theta)
여기에서 Sin(-theta)는 -Sin(theta)이고 Cos(-theta)는 Cos(theta)와 같으므로,
x = x' * Cos(theta) - y' * Sin(theta)
y = x' * Sin(theta) + y' * Cos(theta)
의 공식을 사용해 프로그램에서 이미지를 회전시킬 수 가 있습니다.
출력 이미지의 각 픽셀에 대해서, 프로그램은 출력 픽셀에 매핑되는 입력 이미지내의 점을 찾기 위해 위의 공식을 사용합니다. 그리고 나서, EnlargePicture에서 했던 것처럼 출력 픽셀의 칼라 값을 찾기 위해 네개의 근처 픽셀의 칼라 값에 대한 가중치 평균을 계산합니다.
자료실에 있는 소스를 보시면 RotatePicture라는 프로시져가 있는데, 이것이 회전을 위한 루틴입니다. 이 코드에서는 위에 설명했던것과는 조금 다르게 이미지 왼쪽 상단 모서리가 아닌, 이미지의 중심을 기준으로 해서 회전을 시킵니다. 그렇기 때문에, 계산이 진행되기 전에 출력 픽셀의 위치로부터 (to_cx, to_cy)값이 빼졌고, 결과에 (from_cx, from_cy)가 더해졌습니다.
마지막으로, 회전을 할 경우에 고려해야할 사항이 하나 더 있습니다.
회전이 되면 원본 이미지보다 바깥쪽으로 모서리가 벗어날 수 있어서 짤리게 됩니다. 180' 회전이 아니면, 거의 항상 그렇죠? 그래서, GetRotateSize라는 프로시져를 이용해 출력될 이미지의 높이와 너비를 계산하게 됩니다. 아래에 그 공식이 나와 있습니다.
new_width = Round(Abs(old_width * Cos(theta)) + Abs(old_height * Sin(theta)));
new_height = Round(Abs(old_width * Sin(theta)) + Abs(old_height * Cos(theta)));
그래두 그림 축소 저장하는거..
다음 아랫줄 만큼 간단한건 없을껄요..
단지.. 진짜루 화질이 않조을 뿐이징.. 푸하하..
procedure TForm1.Button3Click(Sender: TObject);
var
Buf: TBitmap;
begin
Buf := TBitmap.Create;
// Buf.Monochrome := True;
Buf.Width := Screen.Width div 4;
Buf.Height := Screen.Height div 4;
StretchBlt(Buf.Canvas.Handle, 0, 0, Buf.Width, Buf.Height,
GetDC(0), 0, 0, Screen.Width, Screen.Height, SRCCOPY);
Buf.SaveToFile('Res.bmp');
Buf.Free;
end;
한델공식동호회가 될 델세상(http://www.freechal.com/delphiworld)의 조규춘이였습니다.
(내가 지금까지 한델에서 답변한것중에 제일 긴 내용이였습니다.